Are all assignments in SPIN atomic?
ags
Posts: 386
I'm battling a bug that looks like cog contention. I'm turning over every rock for the third time.
Is it safe to say that all assignments in SPIN are atomic operations? By this, I mean that there are no intermediate steps between the initial value and ending value (looking for "half-complete" operations being intercepted by other cogs running, causing problems). For instance:
Are all the intermediate steps stored in a scratch area on the stack, and when completed, the final answer is assigned to "value" (this is what I mean by "atomic") or is value somehow involved in the steps along the way, changing value based on intermediate calculations before the final assignment?
Another example would be:
This could be done (among many methods that produce the same result) as:
temp := op1 | op2
temp := temp | value
value := temp
or
value := value | op1
value := value | op2
The first undergoes intermediate steps before the final result is stored in "value"; the second does not. Is there any guarantee that all assignments will only modify the target exactly once?
Is it safe to say that all assignments in SPIN are atomic operations? By this, I mean that there are no intermediate steps between the initial value and ending value (looking for "half-complete" operations being intercepted by other cogs running, causing problems). For instance:
value := op1 << 4 | op2 & $FF
Are all the intermediate steps stored in a scratch area on the stack, and when completed, the final answer is assigned to "value" (this is what I mean by "atomic") or is value somehow involved in the steps along the way, changing value based on intermediate calculations before the final assignment?
Another example would be:
value |= op1 | op2
This could be done (among many methods that produce the same result) as:
temp := op1 | op2
temp := temp | value
value := temp
or
value := value | op1
value := value | op2
The first undergoes intermediate steps before the final result is stored in "value"; the second does not. Is there any guarantee that all assignments will only modify the target exactly once?
Comments
My guess would be that the "value" is only written once. But life is full of surprises....
If you compile that code with BST it has an option to create a listing file of all the Spin byte codes it generates. You could look in there and see what it actually does.
Failing that you could of course introduce your own temp variable and make sure the final assignment is atomic. If that fixes your code it's a good clue that their was a race going on.
Then again you could arrange for a second cog to read "value", perhaps quickly in PASM, and have it check for unexpected values.
All fetches of values from hub memory are done with the RDBYTE, RDWORD, and RDLONG instructions which are atomic. All stores of values to hub memory are done with the WRBYTE, WRWORD, and WRLONG instructions which are also atomic. Special purpose registers are unique to each cog and can't be accessed from other cogs.
Thanks Mike. That is what I was looking for. It's good and bad: good because it is a good "contract" to have, bad because that means I still haven't found the bug...
Specifically, what I often do is build a command to pass (through PAR) to a cog. That can look like:
My concern was that if the cog happened to loop around and see the value of "command" in an intermediate state, that would cause problems.
It only recently occurred to me that sometimes the fastest (and most compact) way to get data/parameters from SPIN/hub to PASM/cog is not packing information into the least number of longs possible. Granted, reading a long in 7-22 clocks is the fastest thing to do, but when I stopped to think about how many clocks are consumed in SPIN doing shifts, or/and, etc, I've realized that it may be faster, and take less space, to pay the price of pulling multiple partially-utilized longs from hub to cog.
Of course, I don't use BST and can't see the compiled SPIN byte codes (and even if I could it might not help me) so this is just intuition, not based on concrete studies of specific cases.
Yep, getting the fastest overall execution out of interacting spin and pasm programs is not as simple as it appears at first glance. Optimizing how they interact can speed execution times by as much as 400% or more.
The first order of the day should be to get the thing working at all. The code should be simple, clear and obvious.
After that you might want to think about optimizing for speed or space.
In this case, for example, you have been fretting over whether some operator and some parameter arrive in a long atomically. Had you written it using two longs so that you set up the parameter(s) and then set the operator/command this would never have been an issue allowing your mind to time to find the real bug wherever it is.
1) Yes, as I said, I now realize that packing parameters into the minimal number of longs for SPIN/PASM interaction is not necessarily increasing performance. As a matter of fact, it is more likely to be slowing things down. (Side note: I'm now adopting the practice that if the parameters I want to pass to PASM code are already ordered longs on the stack (i.e. they are arguments to the SPIN method that will pass them to a PASM cog) passing the address of the first parameter (on the stack) to the PASM cog (in the par register) is probably the fastest way to get things done - no manipulation/packing in SPIN at all)
2) I agree that it compounds the effort with added complexity to prototype, test and debug code that has (potentially) already been obscured by premature optimizations.