Le framework LMAX / Disruptor
       par Cédric FINANCE et François OSTYN
     cedric.finance@soat.fr / francois.ostyn@soat.fr


                       26 janvier 2012
Qui sommes nous ?
Introduction :
LMAX :
Société spécialisée dans le passage d’ordres de bourse.
Affichage temps réel des cotations, volume de transactions élevé,
respect de l’ordonnancement des ordres et temps d’exécution
réduit.
Disruptor (replicator) :
arme ayant été utilisée pour détruire les réplicateurs dans Stargate SG1
Disruptor :
Framework de programmation concurrente. Résultat des
recherches de la société LMAX.
Le cloud, j’le casse
- On se base sur le principe de l’IoA (Inversion of Architecture).

- Pourquoi ne pas utiliser le maximum de puissance CPU et la
mémoire de nos machines ?

- Toutes ces architectures par strates sont compliquées non ?
La soirée 3T va devenir
UT (Ultimate Technical):
Mais pourquoi la gestion de la
concurrence en Java est si
compliquée ?


          Attention, ces 384 pages sont dangereuses !!!
L’ordre :
// ordre de programmation        // ordre d’exécution (non certain)
  int w = 10;                      int y = 30;
  int x = 20;                      int x = 20;
  int y = 30;                      int b = x * y;
  int z = 40;
                                  int z = 40;
 int a = w + z;                   int w = 10;
 int b = x * y;                   int a = w + z;

De nombreux facteurs peuvent influer : le matériel, les
paramètres de la JVM, le type de GC,... merci je JIT
Avant de continuer quelques chiffres :


L1 cache reference                             0.5 ns
Branch mispredict                              5 ns
L2 cache reference                             7 ns
Mutex lock/unlock                             25 ns
Main memory reference                        100 ns
Compress 1K bytes with Zippy               3,000 ns
Send 2K bytes over 1 Gbps network         20,000 ns
Read 1 MB sequentially from memory       250,000 ns
Round trip within same datacenter        500,000 ns
Disk seek                             10,000,000 ns
Read 1 MB sequentially from disk      20,000,000 ns
Send packet CA->Netherlands->CA      150,000,000 ns
Il faut faire attention aux détails :

Incrémenter un compteur :                    Utiliser un lock :
                                             public static long foo = 0;
static long foo = 0;
                                             public static Lock lock = new Lock();
private static void increment() {
                                             private static void increment() {
  for (long l = 0; l < 500000000L; l++) {
                                                 for (long l = 0; l < 500000000L; l++) {
        foo ++;
                                                   lock.lock();
}
                                                   try {
                                                       foo++;
                                                   } finally {
Utiliser un AtomicLong :                             lock.unlock();
                                             }}}
static AtomicLong foo = new AtomicLong(0);
private static void increment() {
    for (long l = 0; l < 500000000L; l++)
   {foo.getAndIncrement();}
}
Le coût des contentions

Incrémenter un compteur 500 millions de fois...

● One Thread! :! 300 ms

● One Thread (volatile):! 4 700 ms (15x)

● One Thread (Atomic) : 5 700 ms (19x)

● One Thread (Lock)!: 10 000 ms (33x)

● Two Threads (Atomic) : 30 000 ms (100x)

● Two Threads (Lock)!: 224 000 ms (746x)
Soit pas loin de 4 minutes
Découper des chaînes de caractères


● Parallel   (Scala): 440 ops/sec

●   Serial! (Java) : 1768 ops/sec
La parallélisation des traitements n’est pas toujours une bonne solution
Et quid des processeurs ?

Comparaison du temps de découpage des chaines de caractères
en mono-thread sur différents processeurs.




       Les performances peuvent aller du simple au double...
Mais que peut nous apporter le
                  Disruptor
... A part nous sauver des réplicateurs... (Private JOKE)
Ne pas devoir recourir à une architecture multi-thread

Donc, une mise en place plus aisée

De la simplicité pour les tests

Un coût de machines moins élevé

Se remettre en question sur le fonctionnement de Java...
Les problématiques rencontrées par la société LMAX

 - False sharing

 - Memory barrier

 - Context switch

 - CAS (compare and swap)

 - Cache line
le False sharing

 public void run()
 {
     long i = ITERATIONS + 1;
     while (0 != --i)
     {
         longs[arrayIndex].value = i;
     }
 }


 public final static class VolatileLong
 {
     public volatile long value = 0L;
     public long p1, p2, p3, p4, p5, p6; // comment out
 }
le False sharing
le False sharing




Pourquoi ce tel changement de performances?
le False sharing
le False sharing




                   Header data1   data2   p1    p2      p3   p4   p5


                                           Cache Line
Les conclusions que l’on peut tirer...

Les queues (java) sont coûteuses (temps de traitement et lock des ressources)

Le multi-threading est coûteux (contentions sur les ressources communes) et difficile a mettre en œuvre.

Le multi-threading n’est pas le plus efficace en terme de performances.




  Attention, ce cas d’utilisation ne peut pas convenir à tout le monde...
Ce qu’a fait LMAX :

Ecriture d’un framework permettant de partager rapidement les données entre thread sans toutefois
recourir aux mécanismes classiques des locks. C’est ce que nous nommerons Disruptor.
Le pattern Disruptor :




  Les producteurs, le Ring Buffer et les consommateurs sont chacun dans des threads différents.
  Les acteurs : Producteur, Consommateur
  Les objets : RingBuffer, Barrier
  Les stratégies : Wait, Claim

Objectif : ne plus avoir de contentions...
Alors qu’avec une architecture classique nous avions :




      Mais où se trouve ma contention...
Ce que peut apporter le disruptor ?




     C’est mieux mais toujours pas parfait....
Mais est-ce vraiment rapide ?




Conclusion : jusqu’à 5 fois plus rapide... et combien de fois moins cher (maintenance,
matériel,...) et plus constant...
...DEMO...
Comment aider le GIE portabilité ? (pour toi Xavier...)
Conclusion
- Ne jamais croire ce que l’on nous dit, il faut tester, expérimenter, la pratique est
souvent très loin de la théorie

- La concurrence est un outil à manier avec précautions,...

- Pour avoir des performances, chaque détail compte, mais comme le dit Donald
Knuth, «premature optimization is the root of Evil»
Liens :
•http://www.infoq.com/articles/memory_barriers_jvm_concurrency
•http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf
•http://www.slideshare.net/trishagee/understanding-the-disruptor
•http://code.google.com/p/disruptor/
•http://disruptor.googlecode.com/files/Disruptor-1.0.pdf
•http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html
•http://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html
•http://martinfowler.com/articles/lmax.html

Présentation LMAX / Disruptor

  • 1.
    Le framework LMAX/ Disruptor par Cédric FINANCE et François OSTYN cedric.finance@soat.fr / francois.ostyn@soat.fr 26 janvier 2012
  • 2.
  • 3.
  • 4.
    LMAX : Société spécialiséedans le passage d’ordres de bourse. Affichage temps réel des cotations, volume de transactions élevé, respect de l’ordonnancement des ordres et temps d’exécution réduit.
  • 5.
    Disruptor (replicator) : armeayant été utilisée pour détruire les réplicateurs dans Stargate SG1
  • 6.
    Disruptor : Framework deprogrammation concurrente. Résultat des recherches de la société LMAX.
  • 7.
    Le cloud, j’lecasse - On se base sur le principe de l’IoA (Inversion of Architecture). - Pourquoi ne pas utiliser le maximum de puissance CPU et la mémoire de nos machines ? - Toutes ces architectures par strates sont compliquées non ?
  • 8.
    La soirée 3Tva devenir UT (Ultimate Technical):
  • 9.
    Mais pourquoi lagestion de la concurrence en Java est si compliquée ? Attention, ces 384 pages sont dangereuses !!!
  • 10.
    L’ordre : // ordrede programmation // ordre d’exécution (non certain) int w = 10; int y = 30; int x = 20; int x = 20; int y = 30; int b = x * y; int z = 40; int z = 40; int a = w + z; int w = 10; int b = x * y; int a = w + z; De nombreux facteurs peuvent influer : le matériel, les paramètres de la JVM, le type de GC,... merci je JIT
  • 11.
    Avant de continuerquelques chiffres : L1 cache reference 0.5 ns Branch mispredict 5 ns L2 cache reference 7 ns Mutex lock/unlock 25 ns Main memory reference 100 ns Compress 1K bytes with Zippy 3,000 ns Send 2K bytes over 1 Gbps network 20,000 ns Read 1 MB sequentially from memory 250,000 ns Round trip within same datacenter 500,000 ns Disk seek 10,000,000 ns Read 1 MB sequentially from disk 20,000,000 ns Send packet CA->Netherlands->CA 150,000,000 ns
  • 12.
    Il faut faireattention aux détails : Incrémenter un compteur : Utiliser un lock : public static long foo = 0; static long foo = 0; public static Lock lock = new Lock(); private static void increment() { private static void increment() { for (long l = 0; l < 500000000L; l++) { for (long l = 0; l < 500000000L; l++) { foo ++; lock.lock(); } try { foo++; } finally { Utiliser un AtomicLong : lock.unlock(); }}} static AtomicLong foo = new AtomicLong(0); private static void increment() { for (long l = 0; l < 500000000L; l++) {foo.getAndIncrement();} }
  • 13.
    Le coût descontentions Incrémenter un compteur 500 millions de fois... ● One Thread! :! 300 ms ● One Thread (volatile):! 4 700 ms (15x) ● One Thread (Atomic) : 5 700 ms (19x) ● One Thread (Lock)!: 10 000 ms (33x) ● Two Threads (Atomic) : 30 000 ms (100x) ● Two Threads (Lock)!: 224 000 ms (746x) Soit pas loin de 4 minutes
  • 14.
    Découper des chaînesde caractères ● Parallel (Scala): 440 ops/sec ● Serial! (Java) : 1768 ops/sec La parallélisation des traitements n’est pas toujours une bonne solution
  • 15.
    Et quid desprocesseurs ? Comparaison du temps de découpage des chaines de caractères en mono-thread sur différents processeurs. Les performances peuvent aller du simple au double...
  • 16.
    Mais que peutnous apporter le Disruptor ... A part nous sauver des réplicateurs... (Private JOKE)
  • 17.
    Ne pas devoirrecourir à une architecture multi-thread Donc, une mise en place plus aisée De la simplicité pour les tests Un coût de machines moins élevé Se remettre en question sur le fonctionnement de Java...
  • 18.
    Les problématiques rencontréespar la société LMAX - False sharing - Memory barrier - Context switch - CAS (compare and swap) - Cache line
  • 19.
    le False sharing public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } public final static class VolatileLong { public volatile long value = 0L; public long p1, p2, p3, p4, p5, p6; // comment out }
  • 20.
  • 21.
    le False sharing Pourquoice tel changement de performances?
  • 22.
  • 23.
    le False sharing Header data1 data2 p1 p2 p3 p4 p5 Cache Line
  • 24.
    Les conclusions quel’on peut tirer... Les queues (java) sont coûteuses (temps de traitement et lock des ressources) Le multi-threading est coûteux (contentions sur les ressources communes) et difficile a mettre en œuvre. Le multi-threading n’est pas le plus efficace en terme de performances. Attention, ce cas d’utilisation ne peut pas convenir à tout le monde...
  • 25.
    Ce qu’a faitLMAX : Ecriture d’un framework permettant de partager rapidement les données entre thread sans toutefois recourir aux mécanismes classiques des locks. C’est ce que nous nommerons Disruptor.
  • 26.
    Le pattern Disruptor: Les producteurs, le Ring Buffer et les consommateurs sont chacun dans des threads différents. Les acteurs : Producteur, Consommateur Les objets : RingBuffer, Barrier Les stratégies : Wait, Claim Objectif : ne plus avoir de contentions...
  • 27.
    Alors qu’avec unearchitecture classique nous avions : Mais où se trouve ma contention...
  • 28.
    Ce que peutapporter le disruptor ? C’est mieux mais toujours pas parfait....
  • 29.
    Mais est-ce vraimentrapide ? Conclusion : jusqu’à 5 fois plus rapide... et combien de fois moins cher (maintenance, matériel,...) et plus constant...
  • 30.
    ...DEMO... Comment aider leGIE portabilité ? (pour toi Xavier...)
  • 31.
    Conclusion - Ne jamaiscroire ce que l’on nous dit, il faut tester, expérimenter, la pratique est souvent très loin de la théorie - La concurrence est un outil à manier avec précautions,... - Pour avoir des performances, chaque détail compte, mais comme le dit Donald Knuth, «premature optimization is the root of Evil»
  • 32.

Notes de l'éditeur