Common Pitfalls in Writing Lock-Free Algorithms
developers.memsql.com
developers.memsql.com
Any optimization should be measured. Otherwise how do you know if all your hard work is worth it? In the author's example, the fastest implementation was actually _not_ lock-free, it merely used a better spinlock than std::mutex.
It's a good idea to use existing implementations mostly so that you can measure and (hopefully without too much trouble) change out the implementation whenever you need to. Different hardware likely has different performance, so measure often!
Our new laser cutter adage is "cut loads and don't bother measuring because it doesn't take into account the size of your cut"
The author is also using gcc 4.6; perhaps different results would be obtained with gcc 4.8 or recent Clang. If you care enough to think about LFDS, measure measure measure =).
https://github.com/facebook/folly/blob/master/folly/Producer... (code)
https://github.com/facebook/folly/blob/master/folly/docs/Pro... (docs)
Android's SMP Primer does a really great way of covering this as well, with a lot of race examples, so I recommend checking it out as a compliment: http://developer.android.com/training/articles/smp.html
The author of this article (and probably most readers) will be concerned with x86, which has a "processor-consistent" memory model. What isn't mentioned is that ARM and other architectures have an even weaker ("weakly consistent") memory model. The SMP guide above describes these in detail and how they can be observed (race) here: http://developer.android.com/training/articles/smp.html#data...
If you are dealing with low-level problems like this--either in libraries, operating systems, or systems tools and utilities--design for "weakly consistent" memory models, or please leave a huge warning to future developers that it may have subtle and difficult race conditions on those platforms.
Maybe I'm just tired, but OS X and Windows already have atomic lifo's that are much better: http://www.opensource.apple.com/source/Libc/Libc-763.13/i386...
(scroll to bottom, see declaration in /usr/include/libkern/OSAtomic.h)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms68...
For linux, there's llist.h in the kernel, but I don't know of a userland version and I haven't used it.
OSEnqueueAtomic/OSDequeueAtomic and InterlockedSList, by virtue of only being able to push and pop the head, are effectively LIFOs.
This is certainly the case on NVIDIA's CUDA platform, where atomic operations incur a 50% penalty - which can sound like a lot but considering that the lock region can be 20% of runtime. In practice this means a lock free runs in 100 seconds while one with primitive locks runs in 110 seconds (and was easy to write debug and maintain).
Well it may mean that to you, but unfortunately it doesn't mean that to anyone else.
Lock free has a precise definition. If you don't know what it is, why get involved in this discussion?
To understand?
void decrement_reference_counter(rc_base* obj)
{
if (0 == atomic_decrement(obj->rc))
delete obj;
}
This is classic test-then-act, isn't it? What happens if another thread bumps obj->rc after the comparison to 0, but before the deletion? That other thread could find itself referring to a suddenly-deleted object, or am I missing something?Edit: Or perhaps you mean, could cause the garbage collector to run? I don't see why that'd be an issue either.
Basically, my reading of "safe" meant "would not block." A garbage collection cycle stops everything, so is effectively a block, right?
"We currently use boost::atomic for this. Our compiler is gcc 4.6, which currently supports std::atomic, but its implementation is much less efficient than boost’s. "
Really, never replace trust in a compiler by trust in a library.
They're getting down to all the nitty-gritty details and then saying 'a memory barrier is expensive'. It seems contradictory to me.
GCC will get better, other compilers will get better as well, but using another level of indirection (a library) is not usually a good idea.
Oh, and maybe replace the sleep by a sched_yield
And even better, don't have hundreds of threads on a single data structure, especially a constrained one like stack, that's probably the easiest way of having speed gains.