@jpbempel#LockFree @jpbempel#LockFree
Programmation Lock-Free : les techniques des pros
UllinkArchitecte Performance@jpbempelhttp://[email protected]
@jpbempel#LockFree
Agenda 1ere partieMesurer la contention
Copy On Write
Lock striping
Compare-And-Swap
Introduction au Modèle Mémoire Java
@jpbempel#LockFree
Agenda 2eme partieDisruptor & RingBuffer
wait/notify – await/signal
Attente active (Spinning)
Queues lock-free (JCTools)
Ticketage : OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
Librairie de LMAX (Incl. Martin Thompson)
Idées pas neuves, circulars buffers dans Kernel Linux
Portées sur Java
Disruptor
@jpbempel#LockFree @jpbempel#LockFree
Pourquoi ne pas utiliser la CLQ qui est Lock(Wait)-Free ?
•Queue non limitée (unbounded) et non bloquante•Alloue un node à chaque insertion (GC)•Pas très sympa avec le cache CPU•MultiProducteur/MultiConsommateur
Array/LinkedBlockingQueue: Pas Lock-free
Disruptor
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : 1P 1C
Object[] ringBuffer;volatile int head;volatile int tail;
public boolean offer(E e) { if (tail - head == ringBuffer.length) return false; ringBuffer[tail % ringBuffer.length] = e; tail++; // <- volatile write return true;}
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : 1P 1C
public E poll() { if (tail == head) // <- volatile read tail return null; int idx = head % ringBuffer.length; E element = ringBuffer[idx]; ringBuffer[idx] = null; head++; // <- volatile write return element;}
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
AtomicReferenceArray ringBuffer;AtomicLong head;AtomicLong tail;
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
public boolean offer(E e) { long curTail; do { curTail = tail.get() if (curTail - head.get() == ringBuffer.length) return false; } while (!tail.compareAndSet(curTail, curTail + 1)); ringBuffer.set(curTail % ringBuffer.length, e);//volatile write return true;}
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
public E poll() { int index = head.get() % ringBuffer.length; E element = ringBuffer.get(index); if (element == null) return null; ringBuffer.set(index, null); head.set(head.get() + 1); // volatile write return element;}
@jpbempel#LockFree @jpbempel#LockFree
Grande flexibilité pour différents usages (Stratégies)
Très bonne performance
Transfert de données d’un thread à l’autre (Queue)
Disruptor
@jpbempel#LockFree @jpbempel#LockFree
Buffer vide, que fait le thread consommateur ?
Buffer plein, que fait(font) le(s) thread(s) producteur(s)
Insertion d’un élement, réveil du consommateur
wait/notify & await/signal
@jpbempel#LockFree @jpbempel#LockFree
Utilisé pour Guarded blocks ou variables conditionnelles
wait/notify & await/signal
@jpbempel#LockFree @jpbempel#LockFree
Latence + contention pour la mise en sommeil (wait)
Latence + contention pour le réveil
Latence pour le thread réveillé avant d’être schedulé
wait/notify & await/signal
@jpbempel#LockFree @jpbempel#LockFree
Attente active
Très bon pour la réactivité du consommateur
Aucun coût pour le producteur
Brûle un cpu en permanence
Spinning
@jpbempel#LockFree @jpbempel#LockFree
Certains locks sont implémentés avec spin (spinLock)
Blocs Synchronized spinnent un peu sur contention
Utilisation de l’instruction pause (x86)
Spinning
@jpbempel#LockFree @jpbempel#LockFree
Comment éviter de brûler un core ?
Stratégie de dégagement:•Thread.yield()•LockSupport.parkNanos(1)•Object.wait()/Condition.await()/LockSupport.park()
Spinning: backoff
@jpbempel#LockFree @jpbempel#LockFree
Librairie créée par Nitsan Wakart (Azul Systems)
Ensemble de Queues lock-free avec fin degré de choix pour différents usages
•Bounded/unbounded SPSC = Single Producer•Bounded/Unbounded MPSC = Multi Producer•Bounded SPMC/MPMC = Multi Consumer
JCTools
@jpbempel#LockFree @jpbempel#LockFree
Quel intéret d’avoir des type queues différentes ?
=> Optimisations en fonction des cas :
•masque pour modulo•volatile writes (lazySet)•false sharing•memory layout•unsafe
JCTools
@jpbempel#LockFree @jpbempel#LockFree
masque pour modulo•Tableau de puissance de 2•Appliquer un et binaire au lieu d'un modulo
volatile writes (lazySet)•Pas de barrière matériel
JCTools : Optimisations
@jpbempel#LockFree @jpbempel#LockFree
False sharing
Champs distincts sur même ligne de cache (64 octets)
=> Padding
JCTools : Optimisations
@jpbempel#LockFree @jpbempel#LockFree
Memory Layout
Champs sont réorganisés par taille et alignésUtilisation de l'héritage pour s'assurer de l'ordre des champs
JCTools : Optimisations
class A { int f1;}
class B extends A { long f2;}
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Ticketage :OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
Comment paralléliser des tâches tout en gardant l’ordre ?
exemple: traitement d’un flux video•Lecture d’une image du flux •Traitement de l’image (parallélisable)•écriture du flux transformé (dans l’ordre)
Ticketage
@jpbempel#LockFree @jpbempel#LockFree
Possible de le faire avec Disruptor,mais par un thread consommateur dédié
OrderedScheduler permet de le faire sans :•pas de surcoût communication inter-threads•pas de thread additionnel •pas de stratégie d’attente
L’idée c’est de prendre un ticket...
Ticketage
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
// called by multiple threads (e.g. thread pool)public void execute() { synchronized (this) { FooInput input = read(); BarOutput output = process(input); write(output); }}
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
OrderedScheduler scheduler = new OrderedScheduler();
public void execute() { FooInput input; long ticket; synchronized (this) { input = read(); ticket = scheduler.getNextTicket(); }[...]
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
public void execute() {[...] BarOutput output; try { output = process(input); } catch (Exception ex) { scheduler.trash(ticket); throw new RuntimeException(ex); } scheduler.run(ticket, () => { write(output); });}
@jpbempel#LockFree @jpbempel#LockFree
open source sur GitHub
Ouvert aux PR et aux discussions sur le design
Utilisé en interne
Venez faire de la code review au stand Ullink !
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
RingBuffer: lock-free facile
Wait!
Spin
JCTools
Ordonner sans thread consommateur
Ce qu’il faut retenir
@jpbempel#LockFree
Références•circular buffers: http://lwn.net/Articles/378262/•Mr T queues: https://github.com/mjpt777/examples/tree/master/src/java/uk/co/real_logic/queues
•Lock-free algorithms by Mr T: http://www.infoq.com/presentations/Lock-free-Algorithms
•Futex are tricky U. Drepper: http://www.akkadia.org/drepper/futex.pdf•JCTools: https://github.com/JCTools/JCTools•Nitsan Wakart blog: http://psy-lob-saw.blogspot.com
@jpbempel#LockFree
Références•Exploiting data parallelism in ordered data streams: https://software.intel.com/en-us/articles/exploiting-data-parallelism-in-ordered-data-streams
•OrderedScheduler: https://github.com/Ullink/ordered-scheduler•Concurrency Freaks: http://concurrencyfreaks.blogspot.com•Preshing on programming: http://preshing.com/•Is Parallel Programming Hard, And If So, What Can You Do About It? https://kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2015.01.31a.pdf
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Q & A
UllinkArchitecte Performance@jpbempelhttp://[email protected]