Initialisation dynamique en c++11 (n2660)

51
Initialisation dynamique en C++11 (n2660) Daniel Strul - 19/12/2015

Transcript of Initialisation dynamique en c++11 (n2660)

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 = 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

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 singletonSingleton retournant

une référenceSingleton 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 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

Le problème du mutexage du singleton

Exemples de singleton mutexéSingleton retournant

une référenceSingleton retournant

un pointeur

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

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 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

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