5 ms·
Yep - it should be enough to force a coherent view for all as part of the initialization.
by vy8vWJlco 13y ago
Yep - 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.
- WalterBright 13y agoIf multiple threads are accessing two globals, two threads are not guaranteed to see changes in one or the other in the same order. This is called "sequential consistency". The only way to get sequential consistency between threads is to insert memory barriers or use synchronization. There is no way around this. Your statement that memory barriers could be skipped is exactly the source of the double checked locking bug. I know this is hard to understand, which is why it has tripped up a looong list of very smart people over the years. David's article solves the problem by having one of the globals not be global at all, but thread local. As for performance, David's article shows with benchmarks that his solution is a huge win.
- rbehrends 13y agoThe POSIX standard requires a full memory barrier for the underlying operations. The problem is that synchronization is expensive: not only because of the memory barriers, which have to include the most expensive ones, but also because locking and unlocking operations need to access main memory directly, bypassing all caches. The problem is, in any event, not the synchronization, but the common path -- for which double-checked locking tries to avoid synchronization --, which needs at a minimum a load-load barrier. That can be done efficiently on TSO architectures (where a load-load barrier is a no-op), but is not guaranteed to be fast on other architectures.