SlideShare une entreprise Scribd logo
1  sur  51
Télécharger pour lire hors ligne
Initialisation dynamique en C++11
(n2660)
Daniel Strul - 19/12/2015
Disclaimer
• Vous n'allez rien apprendre d'utile ☺
– En C++03, les variables globales c'est le mal...
– En C++11, c'est un peu mieux...
• Au menu
– Le static initialization order fiasco (SIOF)
– Le problème du singleton en C++03
– Le double-checked locking pattern (DCLP)
– Les magic statics gcc et C++11
Le problème des
variables statiques non-locales
Les variables statiques non-locales
• Ce sont
– Les variables globales
– Les variables membres statiques
• Initialisation
– Statique: à la compilation
– Dynamique: au lancement du programme
Compter jusqu'à 2 avec des variables statiques...
• Si on est chanceux, ça marche...
• Mais si on n'a pas de chance...
Compter jusqu'à 2... ou pas!
Qu'est-ce qui se passe?
Initialisation
Ordre des
initialisations
Un.c puis Deux.c Deux.c puis Un.c
1ère initialisation UN = abs(1) = 1 DEUX = UN +1 = 0 + 1 = 1
2ème initialisation DEUX = UN + 1 = 1+ 1 = 2 UN = abs(1) = 1
➢ L'ordre d'initialisation des variables est indéterminé
Le compilateur est buggué?
Objects with static storage duration defined in
namespace scope in the same translation unit
and dynamically initialized shall be initialized
in the order in which their definition appears
in the translation unit.
➢ Pour des variables statiques non-locales définies dans un même fichier
source, l'initialisation dynamique se fait dans l'ordre
➢ Aucune contrainte si les variables sont définies dans des fichiers séparés
C++03, clause 3.6.2, "Initialization of non-local objects"
C'est le static initialization order fiasco (SIOF)
What’s the “static initialization order fiasco”?
A subtle way to crash your program.
Suppose you have two static objects x and y in
separate source files, say x.cpp and y.cpp.
Suppose that the initialization for y calls some
method on x.
That’s it. It’s that simple.
You have a 50%-50% chance of dying.
Marshall Cline, Bjarne Stroustrup et al., "C++ Super-FAQ"
Qu'a changé C++11?
Static-duration object initialization may constitute a large
fraction of process time, particularly when users are waiting
for program start, and hence may benefit from parallel
execution.
In the sequential 2003 standard, unspecified order of
initialization essentially required some serial order of
initialization.
In the parallel proposed standard, the unspecified order of
initialization admits concurrent initialization.
L. Crowl, n2660, "Dynamic Initialization and Destruction with Concurrency"
➢ C++11 parallélise l'initialisation pour accéler le lancement des
programmes
En C++11, le SIOF se parallélise
➢ Dans un même fichier source: inchangé (sauf cas particuliers)
➢ Fichiers distincts en monothread: inchangée (ordre indéterminé)
➢ Fichiers distincts en multithread: initialisation concurrente possible
Variables with ordered initialization defined within a single
translation unit shall be initialized in the order of their
definitions in the translation unit.
If a program starts a thread, the subsequent initialization of a
variable is unsequenced with respect to the initialization of a
variable defined in a different translation unit.
Otherwise, the initialization of a variable is indeterminately
sequenced with respect to the initialization of a variable defined
in a different translation unit.
C++11 clause 3.6.2, "Initialization of non-local variables"
La solution c'est le singleton!?
Move each non-local static object into its own
function, where it's declared static.
These functions return references to the objects they
contain.
In other words, non-local static objects are replaced
with local static objects.
Aficionados of design patterns will recognize this as a
common implementation of the Singleton pattern.
Scott Meyers, "Effective C++, 3rd ed.", 2005
➢ Le singleton est la solution la plus courante au SIOF
➢ Il repose sur l'initialisation dynamique des variables statiques locales
Le problème du singleton
Les variables statiques locales
• Ce sont
– Les statiques définies dans une fonction / un bloc
• Initialisation
– Au 1er passage dans la fonction / le bloc
Deux variantes possibles du singleton
Singleton retournant
une référence
Singleton retournant
un pointeur
➢ Il y a de nombreuses autres variantes
➢ On trouve également des templates de singletons
Test d'utilisation concurrente d'un singleton
avec gcc (avant les magic statics)
Le singleton se démultiplie...
Sans lock, l'initialisation du singleton n'est pas thread-safe...
... on peut le construire plusieurs fois ou en fabriquer plusieurs
Test d'utilisation concurrente d'un singleton
avec Visual Studio
Le singleton se divise...
Sans lock, l'initialisation du singleton n'est pas thread-safe...
... on peut en recevoir un en cours de construction
Test d'utilisation concurrente d'un singleton
retournant un pointeur
Le singleton à pointeur s'atomise...
Sans lock, l'initialisation du singleton n'est pas thread-safe...
... on peut obtenir un pointeur nul
... on pourrait obtenir un pointeur vers un objet en construction
... on pourrait obtenir plusieurs pointeurs différents
Le singleton c'est pas la solution?
Avoid using a lazily-initialized Singleton unless
you really need it.
Use eager initialization instead, i.e., initialize a
resource at the beginning of the program run.
Initializing a singleton resource during single-
threaded program startup is the simplest way to
offer fast, thread-safe singleton access.
S. Meyers et A. Alexandrescu, "C++ and the Perils of Double-Checked Locking",
2004
➢ Meyers recommandait le singleton comme solution au SIOF
➢ Mais en multithread, le singleton pose de nouveaux problèmes
3 états, 2 valeurs, 1 problème
Etat de la variable statique Etat observable
Avant initialisation Pas encore initialisé
Pendant l'initialisation Veuillez patienter???
Après initialisation Initialisé
➢ Il faut un troisième état, "Initialisation en cours, veuiller patienter"...
➢ Bref, il faut un mutex
Le problème du mutexage du singleton
Exemples de singleton mutexé
Singleton retournant
une référence
Singleton retournant
un pointeur
Singleton mutexé et performances
Visual Studio 2013 / x86 gcc 4.9.3 / cygwin x64
sans magic statics
Sans mutex 100 ms 60 ms
Avec mutex 6.3 s 11.5 s
Durée d'exécution pour 100 millions d'appels à getInstance()
➢ Le mutexage du singleton, ça n'est pas vraiment gratuit ☹
Singleton mutexé et efficacité
Gestion du mutex pour 10 millions d'appels à getInstance()
➢ Le mutex n'est utile que pendant la phase d'initialisation
➢ Après, il ne sert à rien, mais on doit le verrouiller quand même ☹
Itération Comportement
1 Verrouillage du mutex pour construction thread-safe
2 Verrouillage du mutex parce qu'il est là n°1
3 Verrouillage du mutex parce qu'il est là n°2
...
10 000 000 Verrouillage du mutex parce qu'il est là n°9 999 999
Le Double-Checked Locking Pattern (DCLP)
Every call to instance must acquire and release the
lock.
Although this implementation is now thread-safe,
the overhead from the excessive locking may be
unacceptable.
A better way to solve this problem is to use Double-
Checked Locking, which is a pattern for optimizing away
unnecessary locking.
Douglas C. Schmidt, "Double-Checked Locking, An Optimization Pattern for
Efficiently Initializing and Accessing Thread-safe Objects", 1997
➢ Le DCLP vise à ne prendre le mutex que quand c'est nécessaire
Principe du Double-Checked Locking Pattern
• On vérifie si le singleton est déjà initialisé
1. S'il ne l'est pas, on verrouille le mutex
2. On vérifie que le singleton n'a pas été initialisé pendant qu'on attendait le lock
3. Si personne n'a initialisé le singleton dans l'intervalle, on l'initialise
Mais DCLP est cassé (Java 1.4) ☹
• Double-Checked Locking is widely cited and used as an
efficient method for implementing lazy initialization in a
multithreaded environment.
• It doesn't work
• There are lots of reasons it doesn't work.
• After understanding those, you may be tempted to try to
devise a way to "fix" the double-checked locking idiom.
• Your fixes will not work: there are more subtle reasons why
your fix won't work.
• Understand those reasons, come up with a better fix, and it
still won't work, because there are even more subtle reasons.
David Bacon et coll., The "Double-Checked Locking is Broken" Declaration,
2000
En C++03 aussi, DCLP est cassé ☹
DCLP is designed to add efficient thread-safety to initialization of
a shared resource (such as a Singleton), but it has a problem:
it’s not reliable.
Furthermore, there’s virtually no portable way to make it
reliable in C++ (or in C) without substantively modifying the
conventional pattern implementation.
To make matters even more interesting, DCLP can fail for
different reasons on uniprocessor and multiprocessor
architectures.
S. Meyers et A. Alexandrescu, "C++ and the Perils of Double-Checked Locking",
2004
Qu'est-ce qui ne marche pas?
1
2
3
4
5
➢ DCLP repose sur un ordre d'exécution très précis
➢ Mais qu'est-ce qui se passe si cet ordre n'est pas respecté?...
Compilation et ordre d'exécution
➢ Conforming implementations are required to
emulate (only) the observable behavior of the [C++]
abstract machine.
➢ This provision is sometimes called the “as-if” rule,
because an implementation is free to disregard any
requirement of this International Standard as long as
the result is as if the requirement had been obeyed,
as far as can be determined from the observable
behavior of the program.
➢ Un compilateur peut réordonner tant que le comportement est inchangé
➢ En C++03, le modèle d'exécution est mono-thread
➢ Le compilateur peut réordonner certaines opérations, et casser DCLP
C++03, clause 1.9, "Program execution"
Architecture et ordre d'exécution
➢ Les opérations peuvent être réordonnées selon l'architecture
➢ L'architecture peut réordonner certaines opérations et casser DCLP
P. E. McKenney, "Memory Barriers: a Hardware View for Software Hackers", 2009
Réparer DCLP avec des barrières... ☹
• To use the DCLP Optimization correctly on some platforms,
CPU-specific instructions must be inserted.
D. C. Schmidt, "Pattern-Oriented Software Architecture" Vol. 2, 2000
Singleton et DCLP en C++11
Ce que change C++11
• Concurrency introduces potential deadlock or data races in
the dynamic initialization and destruction of static-duration
objects.
• The language must introduce new syntax, define
synchronization, or limit programs.
• This proposal breaks the problem into three reasonably
separable parts:
– initialization of function-local static-duration objects,
– initialization of non-local static-duration objects,
– and destruction of all static-duration objects.
L. Crowl, n2148, "Dynamic Initialization and Destruction with Concurrency"
Les magic statics
➢ L'initialisation des variables statiques locales devient thread-safe
➢ Implémenté sous gcc depuis gcc 4.3 (a servi de base au standard)
➢ Implémenté sous Visual Studio dès VS 2015
[A variable with static or thread storage duration] is
initialized the first time control passes through its
declaration.
If control enters the declaration concurrently while
the variable is being initialized, the concurrent
execution shall wait for completion of the initialization.
C++11 clause 6.7, "Declaration statement"
3 états, 2 valeurs, 1 solution
Etat de la variable statique Etat observable
Avant initialisation Pas encore initialisé
Pendant l'initialisation Veuillez patienter...
Après initialisation Initialisé
➢ On a notre troisième état, "Initialisation en cours, veuiller patienter"
Le singleton devient thread-safe
Singleton retournant
une référence
Singleton retournant
un pointeur
➢ La thread-safety de l'initialisation est intégrée dans le langage
➢ Utiliser un mutex ou DCLP devient inutile
Sous le capot: les magic statics de gcc
➢ C'est DCLP en assembleur
➢ Le compilateur sait implémenter au mieux selon l'architecture
if (initialized) return instance;
acquire(initialisation guard);
if (initialized) return instance;
instance = new Singleton();
release(initialisation guard);
return instance;
En C++03, on pouvait réparer DCLP avec Boost ou TBB
➢ Les libraries fournissaient des objets atomiques
➢ Les libraries géraient l'ordre d'exécution (opaque calls, assembleur...)
En C++11, les objets atomiques intègrent le langage
➢ La syntaxe C++11 est un peu plus lourde que pour Boost ou TBB
➢ L'ordre d'exécution est garanti par le langage (memory model)
Les atomiques C++11 permettent des optimisations fines
➢ Ce type de fine-tuning est à utiliser avec précaution
Jeff Preshing, "Double-Checked Locking is Fixed In C++11"
Singleton, thread-safety et performances
Durée d'exécution pour 100 millions d'appels à getInstance()
➢ Les statiques locales C++11 sont thread-safe et efficaces
➢ Implémenter DCLP pour un singleton est inutile
Thread-safe?
Visual Studio 2013
x86
gcc 4.9.3
cygwin x64
statique C++03 NON 100 ms 60 ms
statique C++03 mutexée OUI 6.3 s 11.5 s
DCLP thread-unsafe NON 410 ms 50 ms
DCLP avec atomic TBB OUI 400 ms 240 ms
DCLP avec atomic C++11 OUI 400 ms 70 ms
Magic static C++11 OUI N/A 70 ms
Qu'est-ce que ça change
pour le développement multi-cible?
Environnement
Windows
Environnement Linux
Statiques locales Thread-unsafe avec VS 2013 Thread-safe avec gcc
Magic statics Thread-safe avec VS 2015 et gcc
➢ Etre iso sur toutes les cibles, c'est mieux ☺
DCLP sert encore à quelque chose?
➢ Le singleton est un cas particulier de l'idiome d'initialisation tardive (lazy)
➢ Les objets doivent être connus à l'avance (un objet ⇔ une magic static)
➢ DCLP reste utile si le nombre d'objets est variable (cache dynamique)
Calcul et caching de la
somme des données
L'accumulateur est construit
à partir d'un vecteur de
données
DCLP pour implémenter un
calcul lazy de la somme
des données
DCLP dans le langage avec std::call_once
➢std::call_once encapsule l'appel à une fonction
➢ La fonction ne sera appelée qu'une seule fois au maximum
Conclusions
Qu'est-ce qui change en C++11?
• Initialisation dynamique des statiques non-locales
– Pas de changement majeur en monothread (SIOF!)
– En multithread, l'initialisation peut être concurrente
• Initialisation des statiques locales et singleton
– C++11 intègre les magic statics
– L'initialisation du singleton est toujours thread-safe
– DCLP est inutile pour le singleton
• DCLP
– DCLP est réparé avec les atomiques et le memory-model
– A connaître pour le caching dynamique
– Peut-être réalisé avec std::call_once
Disclaimer
• Vous n'avez rien appris de très utile... ☺
• Mais quand même...
– Pensez au static initialization order fiasco
– Pensez à DCLP/call_once pour les caches dynamiques
– Remplacer les atomiques Boost/TBB par C++11?
• Au menu pour un prochain tutorial
– Grandeur et déclin du mot-clé volatile
– Le memory model de C++11

Contenu connexe

Similaire à Initialisation dynamique en c++11 (n2660)

Spring 3 en production
Spring 3 en productionSpring 3 en production
Spring 3 en productionJulien Dubois
 
Amina 2010 workshop slides final version
Amina 2010 workshop slides final versionAmina 2010 workshop slides final version
Amina 2010 workshop slides final versionRMwebsite
 
Déploiements avec Docker
Déploiements avec DockerDéploiements avec Docker
Déploiements avec DockerLuis Lopez
 
Noyau temps réel freertos cheriet mohammed el amine
Noyau temps réel freertos cheriet mohammed el amineNoyau temps réel freertos cheriet mohammed el amine
Noyau temps réel freertos cheriet mohammed el amineCHERIET Mohammed El Amine
 
White paper: SSTIC 2008: Advanced CSRF
White paper: SSTIC 2008: Advanced CSRFWhite paper: SSTIC 2008: Advanced CSRF
White paper: SSTIC 2008: Advanced CSRFManfred Touron
 
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?Christophe HERAL
 
SdE 8 - Synchronization de execution
SdE 8 - Synchronization de executionSdE 8 - Synchronization de execution
SdE 8 - Synchronization de executionAlexandru Radovici
 
L' Open data vu du Cloud computing
L' Open data vu du Cloud computing L' Open data vu du Cloud computing
L' Open data vu du Cloud computing Jonathan Le Lous
 
Rapide introduction au concepts du Chaos Engineering
Rapide introduction au concepts du Chaos EngineeringRapide introduction au concepts du Chaos Engineering
Rapide introduction au concepts du Chaos EngineeringSylvain Hellegouarch
 
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...Pierre-jean Texier
 
Présentation LMAX Disruptor So@t
Présentation LMAX Disruptor So@tPrésentation LMAX Disruptor So@t
Présentation LMAX Disruptor So@tFrancois Ostyn
 
Présentation LMAX / Disruptor
Présentation LMAX / DisruptorPrésentation LMAX / Disruptor
Présentation LMAX / DisruptorSOAT
 
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...Morgan Magnin
 
Apache Storm - Introduction au traitement temps-réel avec Storm
Apache Storm - Introduction au traitement temps-réel avec StormApache Storm - Introduction au traitement temps-réel avec Storm
Apache Storm - Introduction au traitement temps-réel avec StormParis_Storm_UG
 
Paris stormusergroup intrudocution
Paris stormusergroup intrudocutionParis stormusergroup intrudocution
Paris stormusergroup intrudocutionParis_Storm_UG
 

Similaire à Initialisation dynamique en c++11 (n2660) (20)

Spring 3 en production
Spring 3 en productionSpring 3 en production
Spring 3 en production
 
Clonage d'objets
Clonage d'objetsClonage d'objets
Clonage d'objets
 
Amina 2010 workshop slides final version
Amina 2010 workshop slides final versionAmina 2010 workshop slides final version
Amina 2010 workshop slides final version
 
Déploiements avec Docker
Déploiements avec DockerDéploiements avec Docker
Déploiements avec Docker
 
Noyau temps réel freertos cheriet mohammed el amine
Noyau temps réel freertos cheriet mohammed el amineNoyau temps réel freertos cheriet mohammed el amine
Noyau temps réel freertos cheriet mohammed el amine
 
White paper: SSTIC 2008: Advanced CSRF
White paper: SSTIC 2008: Advanced CSRFWhite paper: SSTIC 2008: Advanced CSRF
White paper: SSTIC 2008: Advanced CSRF
 
Promises Javascript
Promises JavascriptPromises Javascript
Promises Javascript
 
Introduction a Java
Introduction a JavaIntroduction a Java
Introduction a Java
 
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?
[Agile Tour Paris 2014] Comment rendre testable du code qui ne l'est pas ?
 
SdE 8 - Synchronization de execution
SdE 8 - Synchronization de executionSdE 8 - Synchronization de execution
SdE 8 - Synchronization de execution
 
L' Open data vu du Cloud computing
L' Open data vu du Cloud computing L' Open data vu du Cloud computing
L' Open data vu du Cloud computing
 
Rapide introduction au concepts du Chaos Engineering
Rapide introduction au concepts du Chaos EngineeringRapide introduction au concepts du Chaos Engineering
Rapide introduction au concepts du Chaos Engineering
 
§T-ydée
§T-ydée§T-ydée
§T-ydée
 
§T-ydée.pdf
§T-ydée.pdf§T-ydée.pdf
§T-ydée.pdf
 
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...
Mises à jour logicielles en environnement Linux Embarqué, petit guide et tour...
 
Présentation LMAX Disruptor So@t
Présentation LMAX Disruptor So@tPrésentation LMAX Disruptor So@t
Présentation LMAX Disruptor So@t
 
Présentation LMAX / Disruptor
Présentation LMAX / DisruptorPrésentation LMAX / Disruptor
Présentation LMAX / Disruptor
 
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...
Comprendre la dynamique des systèmes biologiques grâce à la modélisation hybr...
 
Apache Storm - Introduction au traitement temps-réel avec Storm
Apache Storm - Introduction au traitement temps-réel avec StormApache Storm - Introduction au traitement temps-réel avec Storm
Apache Storm - Introduction au traitement temps-réel avec Storm
 
Paris stormusergroup intrudocution
Paris stormusergroup intrudocutionParis stormusergroup intrudocution
Paris stormusergroup intrudocution
 

Initialisation dynamique en c++11 (n2660)

  • 1. Initialisation dynamique en C++11 (n2660) Daniel Strul - 19/12/2015
  • 2. Disclaimer • Vous n'allez rien apprendre d'utile ☺ – En C++03, les variables globales c'est le mal... – En C++11, c'est un peu mieux... • Au menu – Le static initialization order fiasco (SIOF) – Le problème du singleton en C++03 – Le double-checked locking pattern (DCLP) – Les magic statics gcc et C++11
  • 3. Le problème des variables statiques non-locales
  • 4. Les variables statiques non-locales • Ce sont – Les variables globales – Les variables membres statiques • Initialisation – Statique: à la compilation – Dynamique: au lancement du programme
  • 5. Compter jusqu'à 2 avec des variables statiques...
  • 6. • Si on est chanceux, ça marche... • Mais si on n'a pas de chance... Compter jusqu'à 2... ou pas!
  • 7. Qu'est-ce qui se passe? Initialisation Ordre des initialisations Un.c puis Deux.c Deux.c puis Un.c 1ère initialisation UN = abs(1) = 1 DEUX = UN +1 = 0 + 1 = 1 2ème initialisation DEUX = UN + 1 = 1+ 1 = 2 UN = abs(1) = 1 ➢ L'ordre d'initialisation des variables est indéterminé
  • 8. Le compilateur est buggué? Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit. ➢ Pour des variables statiques non-locales définies dans un même fichier source, l'initialisation dynamique se fait dans l'ordre ➢ Aucune contrainte si les variables sont définies dans des fichiers séparés C++03, clause 3.6.2, "Initialization of non-local objects"
  • 9. C'est le static initialization order fiasco (SIOF) What’s the “static initialization order fiasco”? A subtle way to crash your program. Suppose you have two static objects x and y in separate source files, say x.cpp and y.cpp. Suppose that the initialization for y calls some method on x. That’s it. It’s that simple. You have a 50%-50% chance of dying. Marshall Cline, Bjarne Stroustrup et al., "C++ Super-FAQ"
  • 10. Qu'a changé C++11? Static-duration object initialization may constitute a large fraction of process time, particularly when users are waiting for program start, and hence may benefit from parallel execution. In the sequential 2003 standard, unspecified order of initialization essentially required some serial order of initialization. In the parallel proposed standard, the unspecified order of initialization admits concurrent initialization. L. Crowl, n2660, "Dynamic Initialization and Destruction with Concurrency" ➢ C++11 parallélise l'initialisation pour accéler le lancement des programmes
  • 11. En C++11, le SIOF se parallélise ➢ Dans un même fichier source: inchangé (sauf cas particuliers) ➢ Fichiers distincts en monothread: inchangée (ordre indéterminé) ➢ Fichiers distincts en multithread: initialisation concurrente possible Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit. If a program starts a thread, the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. C++11 clause 3.6.2, "Initialization of non-local variables"
  • 12. La solution c'est le singleton!? Move each non-local static object into its own function, where it's declared static. These functions return references to the objects they contain. In other words, non-local static objects are replaced with local static objects. Aficionados of design patterns will recognize this as a common implementation of the Singleton pattern. Scott Meyers, "Effective C++, 3rd ed.", 2005 ➢ Le singleton est la solution la plus courante au SIOF ➢ Il repose sur l'initialisation dynamique des variables statiques locales
  • 13. Le problème du singleton
  • 14. Les variables statiques locales • Ce sont – Les statiques définies dans une fonction / un bloc • Initialisation – Au 1er passage dans la fonction / le bloc
  • 15. Deux variantes possibles du singleton Singleton retournant une référence Singleton retournant un pointeur ➢ Il y a de nombreuses autres variantes ➢ On trouve également des templates de singletons
  • 16. Test d'utilisation concurrente d'un singleton avec gcc (avant les magic statics)
  • 17. Le singleton se démultiplie... Sans lock, l'initialisation du singleton n'est pas thread-safe... ... on peut le construire plusieurs fois ou en fabriquer plusieurs
  • 18. Test d'utilisation concurrente d'un singleton avec Visual Studio
  • 19. Le singleton se divise... Sans lock, l'initialisation du singleton n'est pas thread-safe... ... on peut en recevoir un en cours de construction
  • 20. Test d'utilisation concurrente d'un singleton retournant un pointeur
  • 21. Le singleton à pointeur s'atomise... Sans lock, l'initialisation du singleton n'est pas thread-safe... ... on peut obtenir un pointeur nul ... on pourrait obtenir un pointeur vers un objet en construction ... on pourrait obtenir plusieurs pointeurs différents
  • 22. Le singleton c'est pas la solution? Avoid using a lazily-initialized Singleton unless you really need it. Use eager initialization instead, i.e., initialize a resource at the beginning of the program run. Initializing a singleton resource during single- threaded program startup is the simplest way to offer fast, thread-safe singleton access. S. Meyers et A. Alexandrescu, "C++ and the Perils of Double-Checked Locking", 2004 ➢ Meyers recommandait le singleton comme solution au SIOF ➢ Mais en multithread, le singleton pose de nouveaux problèmes
  • 23. 3 états, 2 valeurs, 1 problème Etat de la variable statique Etat observable Avant initialisation Pas encore initialisé Pendant l'initialisation Veuillez patienter??? Après initialisation Initialisé ➢ Il faut un troisième état, "Initialisation en cours, veuiller patienter"... ➢ Bref, il faut un mutex
  • 24. Le problème du mutexage du singleton
  • 25. Exemples de singleton mutexé Singleton retournant une référence Singleton retournant un pointeur
  • 26. Singleton mutexé et performances Visual Studio 2013 / x86 gcc 4.9.3 / cygwin x64 sans magic statics Sans mutex 100 ms 60 ms Avec mutex 6.3 s 11.5 s Durée d'exécution pour 100 millions d'appels à getInstance() ➢ Le mutexage du singleton, ça n'est pas vraiment gratuit ☹
  • 27. Singleton mutexé et efficacité Gestion du mutex pour 10 millions d'appels à getInstance() ➢ Le mutex n'est utile que pendant la phase d'initialisation ➢ Après, il ne sert à rien, mais on doit le verrouiller quand même ☹ Itération Comportement 1 Verrouillage du mutex pour construction thread-safe 2 Verrouillage du mutex parce qu'il est là n°1 3 Verrouillage du mutex parce qu'il est là n°2 ... 10 000 000 Verrouillage du mutex parce qu'il est là n°9 999 999
  • 28. Le Double-Checked Locking Pattern (DCLP) Every call to instance must acquire and release the lock. Although this implementation is now thread-safe, the overhead from the excessive locking may be unacceptable. A better way to solve this problem is to use Double- Checked Locking, which is a pattern for optimizing away unnecessary locking. Douglas C. Schmidt, "Double-Checked Locking, An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects", 1997 ➢ Le DCLP vise à ne prendre le mutex que quand c'est nécessaire
  • 29. Principe du Double-Checked Locking Pattern • On vérifie si le singleton est déjà initialisé 1. S'il ne l'est pas, on verrouille le mutex 2. On vérifie que le singleton n'a pas été initialisé pendant qu'on attendait le lock 3. Si personne n'a initialisé le singleton dans l'intervalle, on l'initialise
  • 30. Mais DCLP est cassé (Java 1.4) ☹ • Double-Checked Locking is widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment. • It doesn't work • There are lots of reasons it doesn't work. • After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. • Your fixes will not work: there are more subtle reasons why your fix won't work. • Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons. David Bacon et coll., The "Double-Checked Locking is Broken" Declaration, 2000
  • 31. En C++03 aussi, DCLP est cassé ☹ DCLP is designed to add efficient thread-safety to initialization of a shared resource (such as a Singleton), but it has a problem: it’s not reliable. Furthermore, there’s virtually no portable way to make it reliable in C++ (or in C) without substantively modifying the conventional pattern implementation. To make matters even more interesting, DCLP can fail for different reasons on uniprocessor and multiprocessor architectures. S. Meyers et A. Alexandrescu, "C++ and the Perils of Double-Checked Locking", 2004
  • 32. Qu'est-ce qui ne marche pas? 1 2 3 4 5 ➢ DCLP repose sur un ordre d'exécution très précis ➢ Mais qu'est-ce qui se passe si cet ordre n'est pas respecté?...
  • 33. Compilation et ordre d'exécution ➢ Conforming implementations are required to emulate (only) the observable behavior of the [C++] abstract machine. ➢ This provision is sometimes called the “as-if” rule, because an implementation is free to disregard any requirement of this International Standard as long as the result is as if the requirement had been obeyed, as far as can be determined from the observable behavior of the program. ➢ Un compilateur peut réordonner tant que le comportement est inchangé ➢ En C++03, le modèle d'exécution est mono-thread ➢ Le compilateur peut réordonner certaines opérations, et casser DCLP C++03, clause 1.9, "Program execution"
  • 34. Architecture et ordre d'exécution ➢ Les opérations peuvent être réordonnées selon l'architecture ➢ L'architecture peut réordonner certaines opérations et casser DCLP P. E. McKenney, "Memory Barriers: a Hardware View for Software Hackers", 2009
  • 35. Réparer DCLP avec des barrières... ☹ • To use the DCLP Optimization correctly on some platforms, CPU-specific instructions must be inserted. D. C. Schmidt, "Pattern-Oriented Software Architecture" Vol. 2, 2000
  • 36. Singleton et DCLP en C++11
  • 37. Ce que change C++11 • Concurrency introduces potential deadlock or data races in the dynamic initialization and destruction of static-duration objects. • The language must introduce new syntax, define synchronization, or limit programs. • This proposal breaks the problem into three reasonably separable parts: – initialization of function-local static-duration objects, – initialization of non-local static-duration objects, – and destruction of all static-duration objects. L. Crowl, n2148, "Dynamic Initialization and Destruction with Concurrency"
  • 38. Les magic statics ➢ L'initialisation des variables statiques locales devient thread-safe ➢ Implémenté sous gcc depuis gcc 4.3 (a servi de base au standard) ➢ Implémenté sous Visual Studio dès VS 2015 [A variable with static or thread storage duration] is initialized the first time control passes through its declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. C++11 clause 6.7, "Declaration statement"
  • 39. 3 états, 2 valeurs, 1 solution Etat de la variable statique Etat observable Avant initialisation Pas encore initialisé Pendant l'initialisation Veuillez patienter... Après initialisation Initialisé ➢ On a notre troisième état, "Initialisation en cours, veuiller patienter"
  • 40. Le singleton devient thread-safe Singleton retournant une référence Singleton retournant un pointeur ➢ La thread-safety de l'initialisation est intégrée dans le langage ➢ Utiliser un mutex ou DCLP devient inutile
  • 41. Sous le capot: les magic statics de gcc ➢ C'est DCLP en assembleur ➢ Le compilateur sait implémenter au mieux selon l'architecture if (initialized) return instance; acquire(initialisation guard); if (initialized) return instance; instance = new Singleton(); release(initialisation guard); return instance;
  • 42. En C++03, on pouvait réparer DCLP avec Boost ou TBB ➢ Les libraries fournissaient des objets atomiques ➢ Les libraries géraient l'ordre d'exécution (opaque calls, assembleur...)
  • 43. En C++11, les objets atomiques intègrent le langage ➢ La syntaxe C++11 est un peu plus lourde que pour Boost ou TBB ➢ L'ordre d'exécution est garanti par le langage (memory model)
  • 44. Les atomiques C++11 permettent des optimisations fines ➢ Ce type de fine-tuning est à utiliser avec précaution Jeff Preshing, "Double-Checked Locking is Fixed In C++11"
  • 45. Singleton, thread-safety et performances Durée d'exécution pour 100 millions d'appels à getInstance() ➢ Les statiques locales C++11 sont thread-safe et efficaces ➢ Implémenter DCLP pour un singleton est inutile Thread-safe? Visual Studio 2013 x86 gcc 4.9.3 cygwin x64 statique C++03 NON 100 ms 60 ms statique C++03 mutexée OUI 6.3 s 11.5 s DCLP thread-unsafe NON 410 ms 50 ms DCLP avec atomic TBB OUI 400 ms 240 ms DCLP avec atomic C++11 OUI 400 ms 70 ms Magic static C++11 OUI N/A 70 ms
  • 46. Qu'est-ce que ça change pour le développement multi-cible? Environnement Windows Environnement Linux Statiques locales Thread-unsafe avec VS 2013 Thread-safe avec gcc Magic statics Thread-safe avec VS 2015 et gcc ➢ Etre iso sur toutes les cibles, c'est mieux ☺
  • 47. DCLP sert encore à quelque chose? ➢ Le singleton est un cas particulier de l'idiome d'initialisation tardive (lazy) ➢ Les objets doivent être connus à l'avance (un objet ⇔ une magic static) ➢ DCLP reste utile si le nombre d'objets est variable (cache dynamique) Calcul et caching de la somme des données L'accumulateur est construit à partir d'un vecteur de données DCLP pour implémenter un calcul lazy de la somme des données
  • 48. DCLP dans le langage avec std::call_once ➢std::call_once encapsule l'appel à une fonction ➢ La fonction ne sera appelée qu'une seule fois au maximum
  • 50. Qu'est-ce qui change en C++11? • Initialisation dynamique des statiques non-locales – Pas de changement majeur en monothread (SIOF!) – En multithread, l'initialisation peut être concurrente • Initialisation des statiques locales et singleton – C++11 intègre les magic statics – L'initialisation du singleton est toujours thread-safe – DCLP est inutile pour le singleton • DCLP – DCLP est réparé avec les atomiques et le memory-model – A connaître pour le caching dynamique – Peut-être réalisé avec std::call_once
  • 51. Disclaimer • Vous n'avez rien appris de très utile... ☺ • Mais quand même... – Pensez au static initialization order fiasco – Pensez à DCLP/call_once pour les caches dynamiques – Remplacer les atomiques Boost/TBB par C++11? • Au menu pour un prochain tutorial – Grandeur et déclin du mot-clé volatile – Le memory model de C++11