-
Notifications
You must be signed in to change notification settings - Fork 846
Remove usage of volatile keyword #2603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
f2b2399 to
85dc3a3
Compare
f27e31a to
3dac98a
Compare
|
Didn't have to remove as much of |
|
Any idea what the machine code for this looks like? |
|
Sweet! I think you should split this into two PRs: One for the volatile removal, and one for starting the migration to use std::atomic. |
SolidWallOfCode
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phil checked the machine code output and it looks the same as with the compiler intrinsics.
|
@SolidWallOfCode the std::atomic operator++ appears to produce identical x86_64 assembly to ink_atomic_increment. EDIT: Thanks to @ushachar for reminding me about godbolt, here is an example of how this is the same: https://godbolt.org/g/6UPVa5 |
|
|
||
| struct ink_aiocb { | ||
| int aio_fildes = 0; | ||
| volatile void *aio_buf = nullptr; /* buffer location */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be an atomic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this patch is doing is removing volatile, which did not give us thread safety. So this shouldn't really change anything with regard to that. If it should be atomic, that might be another issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PSUdaemon We should investigate why each of the variables were volatile and covert them to atomic in this PR. Having the changes to atomics in another PR would make it harder to see what needs/did change to atomic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bryancall Should I do the conversions with std::atomic or ink_atomic? I think I'd prefer the former, especially if we plan to remove the latter soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SolidWallOfCode I don't think aio_buf needs to be an atomic. Can you explain why you think otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PSUdaemon I would use std::atomic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good, cause that's what I did :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think he means on that line. Is this variable shared between threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dragon512 no, it's just a member of a struct for AIO. Each AIO operation should have its own struct. There is a lot of "Oh, we are going to update this then something else has to read it. Better make it volatile so the other thing sees the new data" in the code. Totally useless AFAICT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya this should be removed... it slowing the code down by forcing register load in the pipeline.
|
This looks more extensive than it was on my first review. I am concerned about the whole scale removal of |
|
@SolidWallOfCode that is not my understanding of the |
194cee9 to
4d1bbf6
Compare
|
@zwoop I changed the first commit to not introduce |
|
The article you mentioned glosses over a very valid & important use case of volatile: Volatile is often used in this case to prevent the compiler from using a stale value from a register instead of re-reading the value from RAM. It's true that most (if not all) volatiles you remove in this PR appear to be of the "Oh -- I'm using threads so let's write volatile for no reason other then to kill performance" kind, but it requires a careful look to make sure we're not missing a valid use case.... |
|
@ushachar if you are altering it from another thread you should be using a concurrency primitive to protect it. A mutex or atomic loads and stores. If you are suggesting that maybe someone was misusing |
|
And here is a bit of a contrived example, but shows why using |
4d1bbf6 to
abe1a44
Compare
| static int arg_index = 0; | ||
| static std::atomic<uint64_t> processing_counter; // sequential counter | ||
| static int arg_index; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm not sure about changing unsigned long to uint64_t. Are we sure unsigned long is not uint32_t on any system that TS runs on?
If you're on a 32-bit architecture, with no bus-locking 64-bit load/store instructions, I'm pretty sure using std::atomic<uint64_t> will generate code with retry loops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) We don't support 32bit
b) This is an example plugin and that is just a counter for monotonically increasing values
TBH, I was sort of just being cute by making that change. There was a comment that said "use a mutex" or similar, so I just added atomics.
|
Why are these value volatile in the first place? All this says to the compiler is to not assume the cache is correct. It does not make it atomic, or thread safe. |
|
I am for this. I just don't know our code enough to know which vars have locks and which one do not. I am +1 with using std::atomic. The exists of volatile is not going hurt ( might help a little) it is not a replacement for locks or correct interlock calls ( ie std::atomic<>) |
|
Volatile isn't meant to help with cache incoherency. There is no portable way to deal with cache incoherency. I've only worked with one multi-core processor (back in 2003) with per-core cache where there was not transparent coherency/sniffing between the cores. But this was not by design, it was an admitted manufacturing defect. Otherwise, I've only seen cache incoherency when using DMA. And I've always seen it dealt with by using the asm directives to insert (machine-specific) cache flush and invalidate instructions. Volatile is not helpful for this. Volatile was meant originally for memory mapped I/O. It tells the compiler that all nominal load/stores have to correspond, one-to-one, to machine code loads and stores, which are (nominally) executed in the nominal order given by the program. On a processor with strong memory ordering (like the older x86s have), you don't need memory fences to make sure the nominal machine execution order of loads and stores is the same as the actual execution order. On these machine, if you use types that are "naturally" atomic (with proper address alignment), you can do lock-free multithreading using volatile. Sorry that I cannot give a non-shitty explanation or give a link to a non-shitty explanation. |
|
So anyways, my actual point is, I think we can change volatile to std::atomic, it may help and shouldn't hurt. But it's unlikely to eliminate race conditions in the code. |
abe1a44 to
94c50b7
Compare
|
moving to std::atomic is a good thing but we have to be smart about using it. It adds instructions to make sure certain memory ordering happens correctly... https://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/ This can have performance impacts on the code, if we are unwise in the usage as the link shows. I would say that std::atomic is a replacement for the function such as ink_atomic_decrement, etc . I would say that after this change we should remove all usage of ink_atomic_xxx with std::atomic |
|
@dragon512 that is the plan. Going to do a second PR that does |
SolidWallOfCode
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go big or go home. I understand the concerns about volatile and register caching but I think that that can either be replaced with std::atomic or is irrelevant because the value isn't read outside a lock. We'll just have to keep an eye out for potential thread issues to fix with judicious application of std::atomic.
I think we should remove
volatilefrom our code. Here is an article describing it's correct usage. It's entirely likely that this will mostly replaceink_atomicwithstd::atomicas most of the incorrect usage ofvolatileis around thread safety. I have marked this as WIP and plan to add commits over time. Please feel free to review the current progress or debate the issue entirely.