102 votes

Ce qui est plus efficace : Retourner une valeur ou passer par référence ?

J'étudie actuellement comment écrire du code C++ efficace, et à propos des appels de fonction, une question me vient à l'esprit. En comparant ce pseudo-code de fonction :

not-void function-name () {
    do-something
    return value;
}
int main () {
    ...
    arg = function-name();
    ...
}

avec cette fonction pseudo-code identique :

void function-name (not-void& arg) {
    do-something
    arg = value;
}
int main () {
    ...
    function-name(arg);
    ...
}

Quelle version est la plus efficace, et à quel égard (temps, mémoire, etc.) ? Si cela dépend, alors quand la première version serait-elle plus efficace et quand la seconde le serait-elle ?

Modifier : Pour le contexte, cette question se limite aux différences indépendantes de la plate-forme matérielle, et pour la plupart des logiciels également. Existe-t-il des différences de performances indépendantes de la machine ?

Modifier : Je ne vois pas en quoi il s'agit d'un doublon. L'autre question consiste à comparer le passage par référence (code précédent) au passage par valeur (ci-dessous) :

not-void function-name (not-void arg)

Ce qui n'est pas la même chose que ma question. Je ne cherche pas à savoir quelle est la meilleure façon de transmettre un argument à une fonction. Ma question est de savoir quelle est la meilleure façon de faire passer out un résultat à une variable de la portée extérieure.

12 votes

Pourquoi n'essayez-vous pas ? Cela dépend probablement de votre plate-forme et de votre compilateur. Faites-le un million de fois et profilez-le. De plus, en général, écrivez le code de la manière la plus claire possible et ne vous préoccupez des optimisations que si vous avez besoin d'augmenter les performances.

1 votes

Essayez les deux versions plusieurs millions de fois, tout en chronométrant les appels. Faites-le à la fois sans et avec les optimisations activées. En tenant compte des optimisations de la valeur de retour et de l'élision de la copie, je doute que vous trouviez de grandes différences dans les deux cas.

0 votes

Votre exemple ne fait même pas la même chose, et cela dépend fortement de la taille de l'argument, il est presque toujours plus efficace de passer par référence.

39voto

arodriguezdonaire Points 2660

Tout d'abord, tenez compte du fait que le retour d'un objet sera toujours plus lisible (et très similaire en termes de performances) que sa transmission par référence. Il pourrait donc être plus intéressant pour votre projet de retourner l'objet et d'augmenter la lisibilité sans avoir d'importantes différences de performances. Si vous voulez savoir comment avoir le coût le plus bas, la question est de savoir ce que vous avez besoin de retourner :

  1. Si vous devez retourner un objet simple ou basique, les performances seront similaires dans les deux cas.

  2. Si l'objet est si grand et complexe, le retourner nécessiterait une copie, et cela pourrait être plus lent que de l'avoir comme paramètre référencé, mais cela dépenserait moins de mémoire je pense.

Il faut quand même penser que les compilateurs font beaucoup d'optimisations qui rendent les deux performances très similaires. Voir Élision de copie .

2 votes

Et l'élision de la copie ?

10 votes

En fait, sur x86 - et en ignorant les optimisations du compilateur - les deux créeraient le même code d'assemblage, parce que les valeurs de retour qui sont plus grandes que 1 ou 2 registres ? sont transmises via une région de mémoire, qui est allouée par l'appelant et transmise à l'appelé via un paramètre de pointeur implicite.

11voto

Simple Points 4460

Le retour de l'objet devrait être utilisé dans la plupart des cas en raison d'une option appelée élision de la copie .

Cependant, selon la façon dont votre fonction est destinée à être utilisée, il peut être préférable de transmettre l'objet par référence.

Regardez std::getline par exemple, qui prend un std::string par référence. Cette fonction est destinée à être utilisée comme une condition de boucle et continue de remplir un std::string jusqu'à ce que EOF soit atteint. En utilisant le même std::string permet à l'espace de stockage de l std::string pour être réutilisé à chaque itération de la boucle, ce qui réduit considérablement le nombre d'allocations de mémoire à effectuer.

10voto

David Haim Points 3696

Il faut comprendre que la compilation n'est pas une affaire facile. Il y a de nombreuses considérations à prendre en compte lorsque le compilateur compile votre code.

On ne peut pas répondre simplement à cette question car la norme C++ ne fournit pas d'ABI (abstract binary interface) standard. Chaque compilateur est donc autorisé à compiler le code comme il le souhaite et vous pouvez obtenir des résultats différents à chaque compilation.

Par exemple, sur certains projets, le C++ est compilé dans l'extension gérée de Microsoft CLR (C++/CX). Comme tout ce qui existe est déjà une référence à un objet sur le tas, je suppose qu'il n'y a pas de différence.

La réponse n'est pas plus simple pour les compilations non gérées. Plusieurs questions me viennent à l'esprit lorsque je pense à "Est-ce que XXX tournera plus vite que YYY ?", par exemple :

  • Votre objet est-il sourd-constructible ?
  • Votre compilateur prend-il en charge l'optimisation des valeurs de retour ?
  • Votre objet supporte-t-il la sémantique de la copie uniquement ou celle de la copie et du déplacement ?
  • L'objet est-il emballé de manière contiguë (ex. std::array ) ou bien il a un pointeur vers quelque chose sur le tas (par ex. std::vector ) ?

Si je donne un exemple concret, je pense que sur MSVC++ et GCC, le fait de retourner std::vector par valeur sera la même que de le passer par référence, en raison de l'optimisation de la valeur r, et sera un bit (de quelques nanosecondes) plus rapide que le retour du vecteur par move. Cela peut être complètement différent sur Clang, par exemple.

finalement, le profilage est la seule vraie réponse ici.

5voto

Vality Points 2294

Certaines réponses ont abordé ce sujet, mais je voudrais insister, à la lumière de l'édition

Pour des raisons de contexte, cette question se limite aux différences indépendantes de la plate-forme matérielle, et pour la plupart des logiciels également. Existe-t-il des différences de performances indépendantes de la machine ?

Si c'est là les limites de la question, la réponse est qu'il n'y a pas de réponse. La spécification c++ ne stipule pas comment le retour d'un objet ou le passage par référence est implémenté en termes de performance, seulement la sémantique de ce qu'ils font en termes de code.

Un compilateur est donc libre d'optimiser l'un en un code identique à l'autre en supposant que cela ne crée pas de différence perceptible pour le programmeur.

À la lumière de ce qui précède, je pense qu'il est préférable d'utiliser ce qui est le plus intuitif pour la situation. Si la fonction "renvoie" un objet comme résultat d'une tâche ou d'une requête, renvoyez-le, alors que si la fonction effectue une opération sur un objet appartenant au code extérieur, passez par référence.

Vous ne pouvez pas généraliser les performances sur ce point. Pour commencer, faites ce qui est intuitif et voyez si votre système cible et votre compilateur l'optimisent bien. Si, après le profilage, vous découvrez un problème, modifiez-le si nécessaire.

2voto

nullpointer Points 1135

Cette fonction pseudo-code :

not-void function-name () {
    do-something
    return value;
}

serait mieux utilisé lorsque la valeur renvoyée ne nécessite pas de modifications supplémentaires. Le paramètre passé n'est modifié que dans le function-name . Il n'est plus nécessaire d'y faire référence.


fonction pseudo-code identique par ailleurs :

void function-name (not-void& arg) {
    do-something
    arg = value;
}

serait utile si nous avons une autre méthode modérant la valeur de la même variable comme et nous avons besoin de garder les changements faits à la variable par l'un ou l'autre de l'appel.

void another-function-name (not-void& arg) {
    do-something
    arg = value;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X