web-dev-qa-db-fra.com

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

On prétend parfois que C++ 11/14 peut vous donner un coup de fouet même lors de la compilation de code C++ 98. La justification correspond généralement à la sémantique du déplacement, car dans certains cas, les constructeurs rvalue sont générés automatiquement ou font maintenant partie de la STL. Maintenant, je me demande si ces cas étaient en réalité déjà traités par RVO ou par des optimisations similaires du compilateur.

Ma question est donc de savoir si vous pouvez me donner un exemple concret de code C++ 98 qui, sans modification, s’exécute plus rapidement à l’aide d’un compilateur prenant en charge les nouvelles fonctionnalités du langage. Je comprends qu’un compilateur conforme standard n’est pas obligé de procéder à l’élision de la copie et que, pour cette raison, la sémantique des mouvements pourrait apporter 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, mais plutôt s’il existe un code permettant d’ajouter -std = c ++ 14 à mes drapeaux de compilateur, cela s’exécuterait plus rapidement (évitez les copies, mais si vous peut proposer autre chose que la sémantique, je serais également intéressé)

203
alarge

Je connais cinq catégories générales dans lesquelles la recompilation d'un compilateur C++ 03 en tant que C++ 11 peut entraîner des augmentations de performances illimitées qui n'ont pratiquement aucun rapport avec la qualité de la mise en œuvre. Ce sont toutes des variantes de la sémantique du mouvement.

std::vector réaffecter

struct bar{
  std::vector<int> data;
};
std::vector<bar> 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éaffecté en C++ 03, il le copiait tous les vector dans bar.

En C++ 11, il déplace plutôt le bar::datas, qui est fondamentalement gratuit.

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

Échec NRVO

Lorsque NRVO (l'optimisation du retour nommé) échoue, en C++ 03, il revient sur la copie, sur C++ 11, il revient sur le déplacement. Les échecs de NRVO sont faciles:

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

ou même:

std::vector<int> foo(bool which) {
  std::vector<int> 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 les unes avec les autres. Les deux ne peuvent pas être fusionnés avec la valeur de retour sans fusionner les uns avec les autres.

Le problème fondamental est que l'élision NRVO est fragile et qu'un code dont les modifications ne sont pas proches du site return peut soudainement entraîner une réduction massive des performances à cet endroit sans qu'aucun diagnostic ne soit émis. 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

Elision est également impossible ici:

std::set<int> func(std::set<int> in){
  return in;
}

en C++ 11, cela n’est pas cher: en C++ 03, il n’ya aucun moyen d’éviter la copie. La valeur de retour ne permet pas d'éliminer les arguments des fonctions, car la durée de vie et l'emplacement du paramètre ainsi que la valeur de retour sont gérés par le code appelant.

Cependant, C++ 11 peut passer de l'un à l'autre. (Dans un exemple moins jouet, quelque chose pourrait être fait au 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 déplacement de valeur, ce qui enregistre les copies.

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

en C++ 03, un whatever temporaire est créé, puis copié dans le vecteur v. 2 std::string _ des tampons sont alloués, chacun avec des données identiques et l’un est rejeté.

En C++ 11, un whatever temporaire est créé. Le whatever&&Push_back surcharge alors moves que temporaire dans le vecteur v. Une std::string Le tampon est alloué et déplacé dans le vecteur. Un vide std::string est jeté.

Affectation

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

L'élision ne peut pas se produire avec une cession, mais peut être déplacée.

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

ici some_function renvoie un candidat dont on peut élire mais, comme il n'est pas utilisé pour construire directement un objet, il ne peut pas être élidé. En C++ 03, le contenu du temporaire est copié dans some_value. En C++ 11, il est déplacé dans some_value, qui est fondamentalement gratuit.


Pour obtenir le plein effet de ce qui précède, vous avez besoin d’un compilateur qui synthétise pour vous les constructeurs de mouvements et les affectations.

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

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

clang et gcc implémentent depuis longtemps des constructeurs de mouvements implicites. Le compilateur 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).

220

si vous avez quelque chose comme:

std::vector<int> foo(); // function declaration.
std::vector<int> 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.

45
Jarod42