Showing posts with label false sharing. Show all posts
Showing posts with label false sharing. Show all posts

Thursday, February 2, 2017

Nitsan Wakart on Lock-Free Queues

About a year ago Nitsan Wakart gave a very nice presentation about lock-free queues
Here is the link to vimeo:  https://vimeo.com/131688512

Nitsan works for Azul Systems and he has a blog named Psy-Lobotomy-Saw.

If you're into non-blocking queues then make sure not to miss out this video because it has lots of interesting facts.
Some of the topics he covers are:
  • The difficulty in running proper benchmarks for lock-free queues. IMO the hardest to obtain is determinism.
  • What is throughput on a queue? single-enqueue-single-dequeue or bursts?
  • Throughput vs Latency.
  • Benchmarking empty queues vs non-empty.
  • Warmup on benchmarks.
  • Memory Bounded (circular array or ring buffer) vs Memory Unbounded (singly-linked list).
  • When can we discard the volatile keyword in Java Queues (hint: very rarely), and even some of the masters have made this mistake.
  • False Sharing.

Monday, July 18, 2016

Angelika Langer at GOTO 2014

If you want a soft intro to some of the concurrency features introduced in Java 8, here is a video by Angelika Langer at GOTO 2014


I don't know much about the Future's stuff, but the part about StampedLock and Contended is well explained, with intuitive examples as well.
In fact, she makes it look so easy to use StampedLock and particularly the optimistic API of tryOptimiscRead()/validate() that someone who is new to it may think it is easy... it's not.

IMO using optimistic concurrency is like juggling knifes, and I've ranted about it before and will rant about it again on my next post, but I am happy that people are talking about and explaining it in such detail... too bad they didn't give her enough time to explain all the issues that can occur with tryOptimiscRead()/validate(), but in these conferences time is always short so you never get to present everything you want.


Monday, July 11, 2016

John Cairns Presents - Smashing Atomics

If you're into Java and queues, here is a light intro talk by John Cairns. There's nothing new about it that you haven't already seen on other talks posted here (except for his Disruptor variant), but it's always nice to see how a SeqLock can work, and if you're a newbie to the area, this is not a bad place to start:

Btw, there are a few slides that mention RCU, but just replace that with Copy-On-Write because that's what is being talked about, not the RCU synchronization technique.

Also, the slide on "Lock Freedom" has a few incorrect statements so just ignore it.
Namely, the third statement that says "All threads will make progress in a finite time" is incorrect. Lock-Free methods do not give such a guarantee. To have all threads make progress in a finite number of steps we need to have Wait-Free, and there is no progress condition that gives guarantees about "time", they're all defined in terms of number of steps.
The fourth statement in the "Lock Freedom" slide is also incorrect. There are many lock-free data structures with some methods that are not linearizable. The most well known is Michael-Scott Queue (implemented in java.util.concurrent.ConcurrentLinkedQueue) which is not linearizable for iterations. Linearizability is a "Consistency Model" and Lock-Freedom is a "Progress Condition", and although there is some faint relationship between the two concepts, they are mostly orthogonal.

Friday, June 5, 2015

Lamport's One bit solution in C11

As we mentioned before, Lamport's One Bit Solution is one of the fastest (if not the fastest) software solution to the mutual exclusion problem that uses only loads and stores.
Here is the original algorithm:




Andreia came up with an almost identical solution earlier this year, having only a small variation on the "backoff strategy", which in Lamport's case is spinning on each entry of the array until all have been traversed, while on Andreia's variant the waiting thread just starts over. This gives a (small) statistical advantage so that the threads don't trample over each other, but the basic idea of the algorithm is the same: threads to the right of your entry in the array have priority over you, and you have priority over the threads to the left.
What this implies is that the thread with entry zero in the array has priority over all other threads and can easily starve them. This algorithm is not starvation-free (if you want to see a variation of this algorithm that is starvation-free, then check out CRTurn in our paper).

Here's what Andreia's variant looks like in C11 using memory_order_seq_cst:
while (1) {
    int st = UNLOCKED;
    atomic_store(&states[id*PADRATIO], LOCKED);
    for ( int j = 0; j < id; j += 1 )
        if ( (st = atomic_load(&states[j*PADRATIO])) == LOCKED ) break;
    if (st == LOCKED) {
        atomic_store(&states[id*PADRATIO], UNLOCKED);
        while (1) {
            for ( int j = 0; j < N; j += 1 )
                if ( (st = atomic_load(&states[j*PADRATIO])) == LOCKED ) break;
            if (st == UNLOCKED) break;
            Pause();
        }
        continue;
    }
}
for ( int j = id + 1; j < N; j += 1 )
    while (atomic_load(&states[j*PADRATIO]) == LOCKED) { Pause(); }
CriticalSection( id );
atomic_store(&states[id*PADRATIO], UNLOCKED);


where states[] is an array of atomic_int with N*PADRATIO entries (the maximum number of threads), id is the entry in states[] for the current thread, and PADRATIO is the size of a cache line (in atomic_ints).

In this implementation we are using only sequentially consistent atomics, and this is fine and correct, but it isn't terribly fast. Can we relax some of the atomic operations and still provide a correct algorithm?
Yes!

The most obvious improvement is to change the last atomic_store() to use memory_order_release. This will have no effect on the algorithm itself, because none of the instructions on Critical Section (CS) will be re-ordered with the atomic_store() seen as they are above it. Even when running in a loop, it will not be re-ordered with the first atomic_store() because atomic_stores() with memory_order_release will not be re-ordered.
This may seem like a small improvement, but on x86 it means we've just saved an MFENCE instruction, which improves performance dramatically, at least for the low contention scenario:
Displaying image.png

The table above was taken from this PhD thesis, which I recommend if you want to do a deep dive into the C11/C++1x memory model.


A second optimization that does not change the algorithm, is to replace the sequentially consistent loads in the while() loop before the CS, with memory_order_acquire. Loads with a memory_order_acquire will never be re-ordered with each other or with any instructions below them, which means that they will be "stuck in place", and no instruction of the CS can be re-ordered with these loads.
Notice that they could be re-ordered with an atomic_store() immediately above them, but there is always at least one sequentially consistent atomic_load() above them (either from the first for() loop or from the second for() loop), and above those, the atomic_store() is also sequentially consistent (either the first atomic_store() or the second atomic_store()), and because atomic_stores() and atomic_loads() using memory_order_seq_cst can not be re-ordered, everything is safe and correct.
As you can see from the table above, this optimization will make no difference whatsoever on x86, but on PowerPC and ARM, it will save an hwsync or a dmb, both expensive instructions.

There is one more optimization we could do, but in practice it doesn't save much, so maybe I'll talk about some other time.

Here's what the optimized code looks like:

while (1) {
    int st = UNLOCKED;
    atomic_store(&states[id*PADRATIO], LOCKED);
    for ( int j = 0; j < id; j += 1 )
        if ( (st = atomic_load(&states[j*PADRATIO])) == LOCKED ) break;
    if (st == LOCKED) {
        atomic_store(&states[id*PADRATIO], UNLOCKED);
        while (1) {
            for ( int j = 0; j < N; j += 1 )
                if ( (st = atomic_load(&states[j*PADRATIO])) == LOCKED ) break;
            if (st == UNLOCKED) break;
            Pause();
        }
        continue;
    }
}
for ( int j = id + 1; j < N; j += 1 )
    while (atomic_load_explicit(&states[j*PADRATIO], memory_order_acquire) == LOCKED) { Pause(); }
CriticalSection( id );
atomic_store_explicit(&states[id*PADRATIO], UNLOCKED, memory_order_release);


Tuesday, December 31, 2013

Martin Thompson on Lock-Free algorithms


Recorded at QCon last year, this presentation by Martin Thompson starts with an intro to caches on modern microprocessors, then talks about Consistency models, the x86 memory model, and then talks about languages Memory Model (Java, C++11, Erlang).
The presentation then passes over to Michael Barker which talks about Model Specific Registers.
Martin then talks about Contention and Amdahl's Law, locks, atomic instructions. He then goes into what he calls "lock-free algorithms", but he only covers Queues. He does a comparison study of performance an latency for the ConcurrentLinkedQueue, ArrayBlockingQueue, LinkedBlockingQueue, and others.
Michael then takes back control, and talks about False Sharing and some performance benchmarks they did with and without padding.

http://www.infoq.com/presentations/Lock-free-Algorithms



If you don't know who Martin Thompson is, then check out his blog:
http://mechanical-sympathy.blogspot.co.uk/
and Michael Barker's http://bad-concurrency.blogspot.co.uk/