programmation lock free - les techniques des pros (1ere partie)
Post on 17-Jul-2015
453 Views
Preview:
TRANSCRIPT
@jpbempel#LockFree @jpbempel#LockFree
Programmation Lock-Free : les techniques des pros
UllinkArchitecte Performance@jpbempelhttp://jpbempel.blogspot.comjpbempel@ullink.com
@jpbempel#LockFree
Objectif
Réduction de la contention pour une meilleure scalabilité
@jpbempel#LockFree
Agenda 1ère partieMesurer la contention
Copy On Write
Lock striping
Compare-And-Swap
Introduction au Modèle Mémoire Java
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Immutabilité
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Contention
@jpbempel#LockFree @jpbempel#LockFree
Contention2 threads ou plus essayant d'acquérir un lock
Threads en attente de la libération du lock
Raison numéro 1 pour éviter les locks
Photo de Filip Bogdan
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Measure, don’t guess!Kirk Pepperdine & Jack Shirazi
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Measure, don’t premature!
@jpbempel#LockFree @jpbempel#LockFree
Bloc Synchronized
•Profilers (YourKit, JProfiler, ZVision)•JVMTI native agent•Résultats peuvent être difficiles à exploiter
Mesurer la contention
@jpbempel#LockFree @jpbempel#LockFree
Mesurer la contention
@jpbempel#LockFree @jpbempel#LockFree
Mesurer la contention
@jpbempel#LockFree @jpbempel#LockFree
Mesurer la contentionjava.util.concurrent.Lock
•JVM ne peut aider ici•classes du JDK, code classique•JProfiler peut les profiler•Modification classes j.u.c + bootclasspath (jucProfiler)
@jpbempel#LockFree @jpbempel#LockFree
Mesurer la contention
@jpbempel#LockFree @jpbempel#LockFree
Mesurer la contentionInsertion de compteur de contention•Identifier les endroits où l’acquisition des locks échouent •Incrémenter le compteur
Identifier les locks•Callstack à l’instanciation•logger le statut des compteurs
Comment mesurer les locks existants dans votre code•Modifier les classes du JDK•Réintroduire ces classes dans les bootclasspath
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Copy On Write
@jpbempel#LockFree @jpbempel#LockFree
Copier toutes les données quand il y a modification
Très simple, même chose que l’immutabilité
Même problèmes que l’immutabilité (surcoût copie, GC)
Fonctionne bien pour structures à faible modification
Copy On Write
@jpbempel#LockFree @jpbempel#LockFree
Meilleur exemple dans JDK: CopyOnWriteArrayList
Parfait pour les listes de listeners
Autre usage interne: CopyOnWriteHashMap
Maps de référence
Copy On Write
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Lock striping
@jpbempel#LockFree @jpbempel#LockFree
Réduire la contention en la distribuant
N’enlève pas de locks, mais en ajoute !
Efficacité dépend d’un bon partitionnement (cf HashMap)
Lock striping
@jpbempel#LockFree @jpbempel#LockFree
Meilleur exemple dans JDK: ConcurrentHashMap [JDK7]
Lock striping
@jpbempel#LockFree @jpbempel#LockFree
Relativement simple à implémenter
Peut être très efficace si bon partitionnement
Peut être affiné (nombre de partitions) en fonction de la contention/niveau de concurrence
Lock striping
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Compare-And-Swap
@jpbempel#LockFree @jpbempel#LockFree
Primitive pour n’importe quel algorithme Lock-Free
Utilisé pour implémenter locks et primitives de synchronisations
Géré directement par le CPU (instruction dédiée)
Compare-And-Swap
@jpbempel#LockFree @jpbempel#LockFree
Met à jour de façon atomique un emplacement mémoire par une valeur si la précédente est celle attendue
instruction à 3 arguments:•adresse mémoire (rbx)•valeur attendue (rax)•nouvelle valeur (rcx)
Compare-And-Swap
movabs rax,0x2a movabs rcx,0x2b lock cmpxchg QWORD PTR [rbx],rcx
@jpbempel#LockFree @jpbempel#LockFree
Compare-And-Swap
@jpbempel#LockFree @jpbempel#LockFree
En Java pour les classes AtomicXXX :
L’adresse mémoire est le champ value de la classe
Compare-And-Swap
boolean compareAndSet(expect, update)
@jpbempel#LockFree @jpbempel#LockFree
Incrément atomique avec CAS[JDK7] getAndIncrement():
[JDK8] getAndIncrement():
“intrinsèquifier”:
Compare-And-Swap: AtomicLong
while (true) { long current = get(); long next = current + 1; if (compareAndSet(current, next)) return current; }
return unsafe.getAndAddLong(this, valueOffset, 1L);
movabs rsi,0x1 lock xadd QWORD PTR [rdx+0x10],rsi
@jpbempel#LockFree @jpbempel#LockFree
Echange atomique avec CAS[JDK7] long getAndSet(long newValue):
[JDK8] long getAndSet(long newValue):
“intrinsèquifier”:
Compare-And-Swap: AtomicLong
while (true) { long current = get(); if (compareAndSet(current, newValue)) return current; }
return unsafe.getAndSetLong(this, valueOffset, newValue);
xchg QWORD PTR [rsi+0x10],rdi
@jpbempel#LockFree @jpbempel#LockFree
ReentrantLock est implémenté avec CAS
Si CAS échoue => Lock déjà acquis
Compare-And-Swap: lock
volatile int state;
lock() compareAndSet(0, 1);
unlock(): setState(0);
@jpbempel#LockFree @jpbempel#LockFree
L’algorithme lock-free le plus simple
Utilise CAS pour mettre à jour le pointeur suivant dans la liste chainée
Si CAS échoue, une mise à jour concurrente a eu lieu
Lire la nouvelle valeur, aller à l’élément suivant et renouveler le CAS
Compare-And-Swap: ConcurrentLinkedQ
@jpbempel#LockFree @jpbempel#LockFree
Compare-And-Swap: ConcurrentLinkedQ
@jpbempel#LockFree @jpbempel#LockFree
Compare-And-Swap: ConcurrentLinkedQ
@jpbempel#LockFree @jpbempel#LockFree
Compare-And-Swap: ConcurrentLinkedQ
@jpbempel#LockFree @jpbempel#LockFree
Compare-And-Swap: ConcurrentLinkedQ
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Modèle Mémoire Java(Introduction)
@jpbempel#LockFree @jpbempel#LockFree
Premier langage à avoir un modèle mémoire bien défini:Java JDK5 (2004) avec le JSR 133
C++ obtient le sien en 2011 (C++11)
Avant cela, certaines constructions sont non définies ou différentes en fonction de la plateforme/compilateur (ex : Double Check Locking)
Modèle Mémoire Java
@jpbempel#LockFree @jpbempel#LockFree
Ordonnancement mémoire int a; int b; boolean enabled;
{ { a = 21; enabled = true; b = a * 2; a = 21; enabled = true; b = a * 2; } }
JIT Compiler
@jpbempel#LockFree @jpbempel#LockFree
Ordonnancement mémoire int a; int b; boolean enabled;
Thread 1 Thread 2 { { a = 21; if (enabled) { b = a * 2; int answer = b; enabled = true; process(answer); } } }
@jpbempel#LockFree @jpbempel#LockFree
Ordonnancement mémoire int a; int b; volatile boolean enabled;
Thread 1 Thread 2 { { a = 21; if (enabled) { b = a * 2; int answer = b; enabled = true; process(answer); } } }
@jpbempel#LockFree @jpbempel#LockFree
Peut être à 2 niveaux: Compilateur & Hardware
En fonction de l’architecture du CPU, la barrière n’est pas forcément requise
x86: Modèle fort, réordonnancement limité
Barrière Mémoire
@jpbempel#LockFree @jpbempel#LockFree
Barrière mémoire
@jpbempel#LockFree @jpbempel#LockFree
Champ volatile implique des barrières mémoires
Barrière compilateur : empêche réordonnancement
Barrière matérielle : S’assure que les buffers sont vidés
Sur x86, seule la barrière d’écriture est nécessaire
Barrière mémoire: volatile
lock add DWORD PTR [rsp],0x0
@jpbempel#LockFree @jpbempel#LockFree
CAS est aussi une barrière mémoire
Compilateur : Reconnu par le JIT (Unsafe)
Matérielle : toutes les instructions lock agissent comme une barrière mémoire
Barrière mémoire: CAS
@jpbempel#LockFree @jpbempel#LockFree
blocs synchronized ont aussi des barrières mémoires
Entrée du bloc: Barrière mémoire de lecture
Sortie du bloc: Barrière mémoire d’écriture
Barrière mémoire: synchronized
synchronized (this) { enabled = true; a = 21; b = a * 2; }
@jpbempel#LockFree @jpbempel#LockFree
Méthode des classes AtomicXXX
Barrière mémoire seulement pour le compilateur
Pas de barrière mémoire matérielle pour l’écriture
Garantit toujours l’ordonnancement, mais l’effet ne sera pas immédiat pour les autres threads
Barrière mémoire: lazySet
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Ce qu’il faut retenir
@jpbempel#LockFree @jpbempel#LockFree
Mesurer
Copier
Distribuer
Mettre à jour atomiquement
Ordonner
Ce qu’il faut retenir
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
A suivre...
@jpbempel#LockFree @jpbempel#LockFree
Disruptor & Ring Buffer
wait/notify – await/signal
Attente active (Spinning)
Queues lock-free (JCTools)
Ticketage : OrderedScheduler
A suivre dans la 2ème partie
@jpbempel#LockFree
Références•jucProfiler: http://www.infoq.com/articles/jucprofiler•Java Memory Model Pragmatics: http://shipilev.net/blog/2014/jmm-pragmatics/
•Memory Barriers and JVM Concurrency: http://www.infoq.com/articles/memory_barriers_jvm_concurrency
•JSR 133 (FAQ): http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
•CPU cache flushing fallacy: http://mechanical-sympathy.blogspot.fr/2013/02/cpu-cache-flushing-fallacy.html
•atomic<> Weapons: http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2
@jpbempel#LockFree @YourTwitterHandle@YourTwitterHandle@jpbempel#LockFree
Q & A
UllinkArchitecte Performance@jpbempelhttp://jpbempel.blogspot.comjpbempel@ullink.com
top related