cogid behavior
escher
Posts: 138
in Propeller 1
If I am calling a method foo from a main method, and foo starts 5 cogs via cognew, is it guaranteed that the PASM interpreter invoked for the main method will be cogid 0, and each sequentially started cog from foo will be cogid 1, cogid 2, ... , cogid 6? Thank you!
Comments
I'd be interested to hear why you need to know that. Generally it is not necessary to know cog ids.
The reason I'm asking is because I need the 5 cogs to each have a unique variable from 0 to 4, and since they share the same memory being passed in, I don't see an easy way to do this (they need to all have the same start memory address passed in, so I can't pass each one a reference to a different memory location containing 0, 1, 2, etc.)
My thought was using cogid and subtracting one from each to get those unique variables, but that's not pretty.
I could also perform a waitcnt in my initialization code between each cognew, and increment a shared variable between each one. However I would need to know how long cog initialization takes and to perform the rdbyte, and explicit waits also aren't very pretty.
Here is my code:
This is likely an easily solvable problem, but the restriction of each cog needing the same memory address passed in is a hindrance. Thank you!
COGNEW (SpinMethod < (ParameterList) >, StackPointer )
So why not pass those unique numbers to each new cog as a parameter there. Just change it as you go round that loop starting cogs.
You need to pass a stack pointer as well.
That is OK. You are passing the address of some memory area to the COG. Just set the required unique number into that area somewhere before starting a COG. The started COG can read it and save it internally.
A robust way to do it would be to not use a waitcnt. Rather, have the started COG read the unique id, save it away somewhere, then overwrite that unique id parameter with some invalid value. Meanwhile in the main loop, after starting a COG, add a loop that spins around checking for that invalid value to be written to the unique id space. When it is, exit that loop and proceed to the next COG.
I like that idea, thanks!
In general I don't like to see code that requires waits for some magical amount of time to function correctly. Better to have some concrete indication that whatever you are waiting for is done.
Not always possible mind.
Could'nt COG0 instantiate a single LOCK, by using LOCKNEW, then pass its ID as a parameter to the ones it will be starting afterwards, thru cognew?
This way, it can start as many COGs as there are needed, limited to seven more, for sure.
When each started COG begins running, it'll use the passed LOCK ID to do a LOCKSET, and then check the returned value, until successfuly grabbing the LOCK ID to itself.
Then it will ensure exclusive control over the cur_ID variable, manipulate it as intended, and save its new value, including an internal copy, as its own index, before releasing the LOCK, so the others can now get their chances, one at a time.
COG0 could continualy supervise the whole process, by following the evolution of the curr_ID variable, until it reaches the intended last value AND LOCKSET does return available to itself (COG0), showing that the last involved COG has finalized any internal curr_ID-related task.
It (COG0) could also maintain a timer, so the interval between curr_ID changes could be monitored and saved, if intended.
Then the mean time to start and run the basic setup routine for the other COGs would be known.
As a final acknowledge of end of the whole process, COG0 would execute a LOCKRET to return the LOCK ID to the pool of free ones.
Hope this can be implemented and helps a bit.
Henrique
If the third field bit is set (1), the Hub will start the next available (lowest-numbered inactive) cog and return that cog’s ID in Destination (if the WR effect is specified).
If the third field bit is clear (0), the Hub will start or restart the cog identified by Destination’s fourth field, bits 2:0.
Cognew is a simple case of coginit; sequential order is guaranteed. For reference, here is a thread that talks about the time taken to start a cog:
cognew-synchronous-or-asynchronous
See Mike's comment, 16*512 instructions + fudge factor. Basically it is a number of clock cycles, not absolute time.
If I would have a chance to choose between relying on some cumbersome timing scheme or ensure exclusive control over a shared space, I'll sure would prefer depending of the last one.
Perhaps a little joke could help deciding it:
What do you prefer; a LOCK-shared cabin or a TIME-shared one, at a shopping mall bathroom?
Sorry, I couldn't resist.
Henrique
The issue with relying on actual COG ids is that it all goes to hell if you don't have control over all the code in your program. The beauty of the Prop is that it is very easy to throw other people's code into ones project and not have to worry about where it runs. But that means cog ids cannot be relied on unless one wants to read and understand every object one uses.
@Yanomani Quite so. As I said above I don't like to see code that relies on waiting for some magical periods of time.
I prefer control over that shared space, as you say. That is what my simple solution above does. Without the need for locks.
I like your analogy of the lock shared vs time shared cabin at the shopping mall bathroom.
The problem presented here is a bit different though. In my proposed solution to the problem here the next user of the cabin does not and cannot even exist until the previous user has finished with it
Sure, I've understood your solution and its solidity, but the solely reference of any timing waiting loops had re-glowed some ancient sparks into my brain.
I have an old case with LOCKs (or their absence) and the many times I've prayed for them, to get implemented in some systems I've worked with.
Even a single and solid one, at least, would have spared me a lot of thinkering.
Luckily, when Chip suggested removing locks from the P2 design we persuaded him to keep them. Sometimes they are the only solution to a data sharing problem.
By the way, I love this new word you have invented "thinkering". It's like "tinkering" except you don't actually need to physically do anything. Rather like Einstein's "Gedankenexperiment" but more down to Earth I think.
"‘Thinkering' is a word that the writer Michael Ondaatje coined in his novel The English Patient to express the genesis of concepts in the mind while tinkering with the hands."
https://www.psychologytoday.com/blog/imagine/200808/thinkering
The meaning of which is somewhat opposite to what I said above.
It's the cannibal-related X part of my chromosomes, inherited from one of my mother's grandmothers, pure and genuine ones, that tend to mix up known words (think and tinkering) into a someway new (to me) vocabule and meaning.
Perhaps, also my Xs or Ys could have been influenced by her husband (one of my mother's grandfathers), that was basque.
The mixing of xokleng language speaking people (brazilian "botocudos" indians) and basque from my mother's inheritage with portuguese, spanish , french (the former two surely with a bit of basque too) and arabic, from my father's, resulted in my someway uniqueness, when it concerns to cut or add language-related concepts.
Sure, I've been always influenced by a six month long course in german (1978), where I've learned to create new words, simply by adding two or more already known ones, in a chain, clipping out any extra or unused pieces (the old cannibal mantra; go ahead, just in parts).
I'ts also a Google translate fault (perhaps not, as per your other post), because I tend to type in english (trying to always improve my poor skills, when it comes to typing english words), into the left panel, while looking at the meaning of the translated phrases in portuguese (thinkering is translated, by Google, as having the same meaning as thinking, thus it don't flaggs an exception into my right panel view).
Henrique
P.S. My mother's blood type, O rh-; my father's, A rh+; my own, A rh-, and my sister's (younger than me), O rh+.
It's a true mixing!
Well, escher's question as I understand it was not about random use of other peoples' code, it was specifically about a main program that starts in cog 0 and then starts up 5 new cogs, implying control. It is an application that needs to achieve a specific purpose, not an abstract entity. The cogID facility is there to use and operates in a perfectly deterministic fashion. If the application needs to include another object that starts the 7th cog, the initialization can account for it by thinkering. (I.e. minor forethought may be required!)
I agree with this sentiment entirely, however my issue with using CogID in this fashion is that if someone comes in behind me (or I come back way down the road with a bad memory) and initialize any cogs before those, or make any changes which would disturb the exact cogs and their IDs, then it'll all go to hell.
The locks ended up being the perfect solution for this problem! I would consider them the best practice for this exact kind of problem. My final code is here, with the locks being in the start method and top of the DAT section.
Sure, the way you've made use of locks, is a good example of an application where the closest-possible-to-atomic nature of the changes made at the shared variable(s), does ensure the short time each COG will keep the (also shared) lock resource, under its exclusive control.
Having none internal (to each routine) timing dependencies ("aka" deterministic), during the period that each COG "grabs" the lock to itself, is a good recipe of a successful implementation.
Since I'm not viewing all the code you've done, I can't confirm that a little concern of mine has not been antecipated and solved, elsewhere in the whole code body.
The question is: is there any place, into the rest of the code, where the start procedure is called, as many times as needed, until a TRUE status condition is ensured to be received by the caller routine, and also that no other caller point does exists, executed afterwards, where a returned FALSE status could be accepted?
If so, then there are no concerns at all, and the whole code can be considered to behave the way you intended, based on the use you have made of locks.
But, if there is a chance that a continuous FALSE return is acceptable, the caller routine must ensure the release of the (cog_sem) lock, returning it to the pool of free ones, before giving-up, perhaps with it's own error status code.
Since I'm confident you've absorbed the concept of using locks, and sure has made a good use of it, Its almost certain that the above concerns are a function of my ignorance of the rest of your code.
But, just in case...
Henrique
A simple check will let the last one know it's last.
It sets a flag for your main program to continue knowing all 4 COGS are ready.
One more memory value can specify number of COGS, if ever needed.
Your main program only starts one.
I almost never use par for things that are shared among all instances of a cog. It takes up precious cogram. I try to do all initialization in SPIN before starting the cog.
That's definitely another perfectly reasonable solution! I'm going to stick with the locks as being able to say semaphore and mutex makes my project sound cooler, but I appreciate the suggestion!
Or, do it the other way around - have a LONG in the DAT for the memory pointer, and pass the cog id in through par (but shift it up two bits, since par's bottom two bits don't make it through). That way, you don't need to wait 8,000 clocks between cognews.