La scalabilité des applications est une préoccupation importante. Beaucoup de pertes en scalabilité proviennent de code contenant des locks qui produisent une importante contention en cas de forte charge.
Dans cette présentation nous allons aborder différentes techniques (striping, copy-on-write, ring buffer, spinning, ...) qui vont nous permettre de réduire cette contention ou d'obtenir un code sans lock. Nous expliquerons aussi les concepts de Compare-And-Swap et de barrières mémoires.
12. @jpbempel#LockFree @jpbempel#LockFree
Mesurer la contention
java.util.concurrent.Lock
•JVM ne peut aider ici
•classes du JDK, code classique
•JProfiler peut les profiler
•Modification classes j.u.c + bootclasspath (jucProfiler)
14. @jpbempel#LockFree @jpbempel#LockFree
Mesurer la contention
Insertion 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
16. @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
23. @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
24. @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
27. @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
28. @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
30. @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
36. @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
40. @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
42. @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
43. @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
44. @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;
}
45. @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
49. @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