3 ms·
I don't think thread-local variables are necessary. Consider a global flag that gets instantiated and synchronized across threads/cores to a state of 0 such th
by vy8vWJlco 13y ago
I don't think thread-local variables are necessary.
Consider a global flag that gets instantiated and synchronized across threads/cores to a state of 0 such that if it ever gets set to 1, it was after publishing and synchronizing the shared instance via a global pointer. This pointer, and the global flag, will not revert for the rest of the singleton's life (noted in the article) - so there is nothing to synchronize past that - and it is only for the guarding flag's 0 (unallocated) case that there is a question if things are synchronized. That check and potential allocation are what need a standard lock (provided by D's syncronized block), which the example already provides.
- rbehrends 13y agoTo get such a global flag with the desired behavior, you need explicit memory barriers. Otherwise, you cannot guarantee that it won't become visible to another processor before the object's data itself becomes visible. The problem here is that the memory accesses for the path that bypasses the synchronization block are not guaranteed to occur in the correct order without barriers.
- vy8vWJlco 13y agoYep - it should be enough to force a coherent view for all as part of the initialization.
- rbehrends 13y agoYes, it's well-known that you can fix the problem with memory barriers. But the article's point was that such memory barriers can be avoided entirely for the common execution path if you have fast thread-local storage (because you can make sure that every thread is forced through the full synchronization block once before accessing anything). Given that memory barriers can be expensive on some processors, that's a not insignificant win.
- vy8vWJlco 13y agoDoes D's synchronization not contain memory barriers (I don't know)? (Edited)
- WalterBright 13y agoThe problem with memory barriers is that they are slow. David's solution using TLS avoids them entirely for the usual case, hence it is much faster (as his benchmark results show).
- vy8vWJlco 13y agoThanks for your comment. I do see that the TLS is used in a conditional to avoid the barriers in most cases (the already-initialized case), but the initialization still happens in a synchronized block. I may be wrong, but the docs for D say that effectively wraps the block in a critical section which seems likely to include rw barriers, meaning the code given has barriers at init and also uses TLS. If so, the TLS would be unnecessary, for my reasons above - causing me to suspect that the example has just hidden the barriers behind the synchronized keyword.
- WalterBright 13y agoYes, when it is synchronizing there are memory barriers. But the point is the synchronization and memory barriers can be skipped once the singleton is initialized. The usual case can then run without barriers.
- vy8vWJlco 13y agoThanks again. If I am right though, the memory barriers could also have been skipped without thread-local values since, due to the barriers on init, no thread's view would revert to it's pre-init state anyway. As rbehrends mentions, barriers are not the only factor... For example, global memory access may be slower on some systems due to decreased locality (a property provided by the TLS), but I don't know what if any value that would have in this benchmark. There is also a comment on that page that does the same thing as my TLS-free approach using a more reasonable static global initialization syntax in Java in response to a question of if D's initialization would be semantically equivalent too. Edit: (Not waiting for reply link to appear...) When I say "the memory barriers could also be skipped" I am referring to any other memory barriers past those associated with initialization, not those associated with initialization. There would be memory barriers at initialization, but that is the only need for memory barriers and I believe that is provided here by the synchronized keyword. The object presented in the article then looks like a cache-friendlier version of a single global variable with the necessary barriers limited to the initialization of the global instance pointer (which might also be done as in the Java example given in the comments on the page). The TLS does not eliminate the need for any barriers, only helps eliminate non-local reads. That might allow a performance gain depending on the cache behavior (or, for example, in the presence of NUMA), but it hasn't reduced the need for locking IMHO.