@jpbempel#LockFree @jpbempel#LockFree
Programmation Lock-Free :
les techniques des pros
Ullink
Architecte Performance
@jpbempel
http://jpbempel.blogspot.com
jpbempel@ullink.com
@jpbempel#LockFree
Objectifs
Réduction de la contention pour une meilleure scalabilité
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Précédemment...
@jpbempel#LockFree
Agenda 1ere partie
Mesurer la contention
Copy On Write
Lock striping
Compare-And-Swap
Introduction au Modèle Mémoire Java
@jpbempel#LockFree
Agenda 2eme partie
Disruptor & RingBuffer
wait/notify – await/signal
Attente active (Spinning)
Queues lock-free (JCTools)
Ticketage : OrderedScheduler
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Disruptor & Ring Buffer
@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
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : 1P 1C
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : 1P 1C
@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
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
@jpbempel#LockFree @jpbempel#LockFree
Ring Buffer : nP 1C
@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 @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
wait/notify
await/signal
@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 @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Spinning
@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 @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
JCTools
@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és
Utilisation 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
Ticketage
@jpbempel#LockFree @jpbempel#LockFree
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
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@jpbempel#LockFree @jpbempel#LockFree
OrderedScheduler
@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 @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Ce qu’il faut retenir
@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_l
ogic/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.2
015.01.31a.pdf
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Q & A
Ullink
Architecte Performance
@jpbempel
http://jpbempel.blogspot.com
jpbempel@ullink.com

Programmation lock free - les techniques des pros (2eme partie)