Le C ++ moderne peut-il vous offrir des performances gratuites?

On prétend parfois que C ++ 11/14 peut vous faire gagner en performance, même en compilant simplement du code C ++ 98. La justification se situe généralement dans le sens de la sémantique de mouvement, car dans certains cas, les constructeurs de valeurs Rvalue sont automatiquement générés ou font maintenant partie de la STL. Maintenant, je me demande si ces cas ont déjà été traités par RVO ou des optimisations similaires du compilateur.

Ma question est donc de savoir si vous pourriez me donner un exemple concret de code C ++ 98 qui, sans modification, s’exécute plus rapidement en utilisant un compilateur prenant en charge les nouvelles fonctionnalités du langage. Je comprends qu’un compilateur conforme standard n’est pas nécessaire pour faire l’élision de la copie et que, par conséquent, la sémantique du mouvement pourrait entraîner de la vitesse, mais j’aimerais voir un cas moins pathologique, si vous voulez.

EDIT: Juste pour être clair, je ne demande pas si les nouveaux compilateurs sont plus rapides que les anciens compilateurs, mais plutôt s’il y a du code dans lequel l’ajout de -std = c ++ 14 à mes indicateurs de compilateur serait plus rapide peut faire autre chose que déplacer la sémantique, je serais intéressé aussi)

Je suis au courant de 5 catégories générales dans lesquelles la recompilation d’un compilateur C ++ 03 en C ++ 11 peut entraîner des augmentations de performances illimitées pratiquement sans rapport avec la qualité de la mise en œuvre. Ce sont toutes les variations de la sémantique de mouvement.

std::vector reallocate

 struct bar{ std::vector data; }; std::vector foo(1); foo.back().data.push_back(3); foo.reserve(10); // two allocations and a delete occur in C++03 

Chaque fois que le tampon de foo est réalloué en C ++ 03, il copiait tous les vector de la bar .

En C ++ 11, il déplace la bar::data s, qui est fondamentalement libre.

Dans ce cas, cela repose sur des optimisations à l’intérieur du vector conteneur std . Dans tous les cas ci-dessous, l’utilisation de conteneurs std est juste parce qu’ils sont des objects C ++ qui ont une sémantique de move efficace en C ++ 11 “automatiquement” lorsque vous mettez à niveau votre compilateur. Les objects qui ne le bloquent pas et qui contiennent un conteneur std héritent également des constructeurs de move améliorés automatiques.

Échec de NRVO

Lorsque NRVO (nommé optimisation de la valeur de retour) échoue, en C ++ 03, il recourt à la copie, en C ++ 11, il se remet en mouvement. Les échecs de NRVO sont faciles:

 std::vector foo(int count){ std::vector v; // oops if (count< =0) return std::vector(); v.reserve(count); for(int i=0;i 

ou même:

 std::vector foo(bool which) { std::vector a, b; // do work, filling a and b, using the other for calculations if (which) return a; else return b; } 

Nous avons trois valeurs - la valeur de retour et deux valeurs différentes dans la fonction. Elision permet aux valeurs de la fonction d'être "fusionnées" avec la valeur de retour, mais pas entre elles. Ils ne peuvent pas tous deux être fusionnés avec la valeur de retour sans se fusionner.

Le problème fondamental est que l'élision de la NRVO est fragile et que le code avec des changements qui ne sont pas à proximité du site de return peut soudainement entraîner des réductions de performance massives à cet endroit sans diagnostic. Dans la plupart des cas d'échec NRVO, C ++ 11 se termine par un move , tandis que C ++ 03 se termine par une copie.

Renvoyer un argument de fonction

L'élision est également impossible ici:

 std::set func(std::set in){ return in; } 

en C ++ 11 c'est pas cher: en C ++ 03, il n'y a aucun moyen d'éviter la copie. Les arguments relatifs aux fonctions ne peuvent pas être éludés avec la valeur de retour, car la durée de vie et l'emplacement du paramètre et de la valeur de retour sont gérés par le code d'appel.

Cependant, C ++ 11 peut se déplacer de l'un à l'autre. (Dans un exemple de moindre jouet, quelque chose pourrait être fait sur le set ).

push_back ou insert

Enfin, l’élision dans les conteneurs ne se produit pas: mais C ++ 11 surcharge les opérateurs d’insertion de mouvements rvalue, ce qui permet d’économiser des copies.

 struct whatever { std::ssortingng data; int count; whatever( std::ssortingng d, int c ):data(d), count(c) {} }; std::vector v; v.push_back( whatever("some long ssortingng goes here", 3) ); 

en C ++ 03, un temporaire est créé, puis copié dans le vecteur v . 2 tampons std::ssortingng sont alloués, chacun avec des données identiques, et l'un d'eux est ignoré.

En C ++ 11, un temporaire est créé. La surcharge push_back whatever&& push_back move alors cette valeur temporaire dans le vecteur v . Un tampon std::ssortingng est alloué et déplacé dans le vecteur. Un std::ssortingng vide est supprimé.

Affectation

Volé de la réponse de @ Jarod42 ci-dessous.

L'élision ne peut pas se produire avec l'affectation, mais le déplacement peut.

 std::set some_function(); std::set some_value; // code some_value = some_function(); 

Ici, some_function renvoie un candidat à éliminer, mais comme il n'est pas utilisé pour construire un object directement, il ne peut être élidé. En C ++ 03, le contenu ci-dessus entraîne la copie du contenu temporaire dans some_value . En C ++ 11, il est déplacé dans some_value , ce qui est fondamentalement gratuit.


Pour le plein effet de ce qui précède, vous avez besoin d'un compilateur qui synthétise les constructeurs de déplacement et l'affectation pour vous.

MSVC 2013 implémente les constructeurs de déplacement dans les conteneurs std , mais ne synthétise pas les constructeurs de déplacement sur vos types.

Ainsi, les types contenant std::vector s et similaires n'obtiennent pas de telles améliorations dans MSVC2013, mais commenceront à les obtenir dans MSVC2015.

clang et gcc ont depuis longtemps implémenté des constructeurs de déplacements implicites. Le compilateur d'Intel 2013 prendra en charge la génération implicite de constructeurs de déplacement si vous passez -Qoption,cpp,--gen_move_operations (ils ne le font pas par défaut dans un effort de compatibilité croisée avec MSVC2013).

si vous avez quelque chose comme:

 std::vector foo(); // function declaration. std::vector v; // some code v = foo(); 

Vous avez une copie en C ++ 03, alors que vous avez une affectation de déplacement en C ++ 11. vous avez donc une optimisation gratuite dans ce cas.