Initialisation dynamique en c++11 (n2660)
-
Upload
daniel-strul -
Category
Software
-
view
156 -
download
0
Transcript of Initialisation dynamique en c++11 (n2660)
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
Les variables statiques non-locales• Ce sont
– Les variables globales– Les variables membres statiques
• Initialisation– Statique: à la compilation– Dynamique: au lancement du programme
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 = 12è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
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 singletonSingleton retournant
une référenceSingleton retournant
un pointeur
➢ Il y a de nombreuses autres variantes➢ On trouve également des templates de singletons
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 �
Le singleton se divise...
Sans lock, l'initialisation du singleton n'est pas thread-safe...... on peut en recevoir un en cours de construction �
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 observableAvant 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
Singleton mutexé et performances
Visual Studio 2013 / x86 gcc 4.9.3 / cygwin x64sans 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 mutex2. On vérifie que le singleton n'a pas été initialisé pendant qu'on attendait le lock3. 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
23
45
➢ 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
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 observableAvant 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-safeSingleton retournant
une référenceSingleton 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 2013x86
gcc 4.9.3cygwin 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 changepour 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
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