2009-04-06 6 views
19

L'un d'entre eux est-il plus rapide?Devrais-je prendre des arguments pour aligner les fonctions par référence ou par valeur?

inline int ProcessByValue(int i) 
{ 
    // process i somehow 
} 

inline int ProcessByReference(const int& i) 
{ 
    // process i somehow 
} 

Je sais que les types entiers doivent être passés par valeur. Cependant, je suis préoccupé par le fait que le compilateur pourrait intégrer ProcessByValue pour contenir une copie. Y a-t-il une règle pour cela?

+0

La meilleure chose à faire ici est de l'essayer.Je doute que le compilateur fasse quelque chose (apparemment) stupide, mais je peux me tromper. – Noldorin

+0

Le compilateur fera le travail requis. Si une copie est nécessaire (si i est modifié dans la fonction) alors une copie sera faite. S'il n'a pas besoin de copier la valeur, ce ne sera pas le cas. Quoi qu'il arrive si je suis manipulé de toute façon (comme dans une expression), il sera probablement copié dans un registre de toute façon. –

Répondre

18

Le paramètre doit être saisi en fonction de ce qui est logique pour la fonction.

Si la fonction prend un type primitif, la valeur de passage aurait du sens. Certaines personnes que je connais se plaindraient si elles étaient passées par const ref (car c'est «inutile»), mais je ne pense pas que je m'en plaindrais. Si la fonction prend un type défini par l'utilisateur et ne modifie pas le paramètre, alors passer par const ref aurait du sens.

S'il s'agit d'un type défini par l'utilisateur et que le paramètre est modifié, la sémantique de la fonction dicterait la manière dont elle devrait être transmise.

7

Le compilateur devrait être capable d'optimiser une fonction en ligne afin que l'une ou l'autre méthode génère un code identique. Faites celui qui est le plus clair.

En cas de doute, essayez-le. Activez la sortie de la liste d'assemblage de votre compilateur et voyez s'il y a une différence.

20

Cela ne fait aucune différence. Dans les deux cas, le code sera aligné sur le même. Copier inutilement l'int (en valeur pass-by) sera éliminé par le compilateur, et créer inutilement une référence à l'int, et suivre cette couche d'indirection en accédant à l'int, sera également éliminé.

Votre question semble reposer sur des hypothèses fausses:

  • Que le mot-clé en ligne sera effectivement obtenir votre fonction inline. (Cela peut, mais ce n'est certainement pas garanti)
  • Que le choix de la référence par rapport à la valeur dépend de la fonction en ligne. (Les mêmes considérations de performance s'appliqueraient à une fonction non-insérée)
  • Cela fait une différence, et que vous pouvez déjouer le compilateur avec des changements triviaux comme ceci (Le compilateur appliquera les mêmes optimisations dans les deux cas)
  • Et que l'optimisation ferait réellement une différence mesurable dans la performance. (Même si elle ne l'a pas, la différence serait si faible qu'elle est négligeable.)

Je sais que les types intégrés doivent être passé par valeur. Cependant, je suis concerné que le compilateur peut en ligne ProcessByValue pour contenir une copie . Y a-t-il une règle pour cela?

Oui, une copie est créée. Tout comme passer par référence créerait une référence. Et puis, au moins pour les types simples comme ints, le compilateur éliminerait les deux à nouveau. L'insertion d'une fonction n'est pas autorisée à modifier le comportement d'une fonction. Si vous créez la fonction pour prendre un argument de valeur, il se comportera comme s'il recevait un argument de valeur, qu'il soit ou non inséré. Si vous définissez la fonction pour prendre une référence, elle se comportera comme si elle passait une référence, qu'elle soit ou non insérée. Alors, faites ce qui conduit à un comportement correct.

+0

Est-ce que cela s'applique aux types autres que 'int' ou aux objets volumineux? – alfC

+0

Cela s'applique à tous les types. Bien sûr, pour un 'int', la création d'une copie sera probablement moins chère que pour les grands objets complexes, mais les règles pour quand la copie peut être optimisée, et quand elle doit * être * sont les mêmes – jalf

+0

Je suppose que lorsque la fonction * peut * être en ligne, la copie peut également être optimisée. Le problème est que si la fonction ne peut pas être inline (pour une raison quelconque) alors la copie pourrait ne pas être optimisée et on se retrouve avec un goulot d'étranglement inutile très lent, juste en n'utilisant pas 'const &'. (Je parle de quelque chose de plus grand que 'int' ici). Qu'est-ce que tu penses? – alfC

0

La meilleure façon de comprendre cela est de créer un banc d'essai qui fait à la fois, construire des versions optimisées du code, et vérifier l'ensemble. Vous verrez immédiatement ce qui se passe avec votre compilateur particulier et votre cas d'utilisation particulier.

Quand c'est vraiment le cas, faites ce que vous pensez qu'un utilisateur de votre classe attendrait d'une interface. Lorsque vous avez tout construit et fonctionne, mesurez et découvrez où sont vos goulots d'étranglement. Les chances sont, toute différence que cela pourrait faire (et il est peu probable d'en faire) sera noyée par de plus grandes préoccupations de performance ailleurs dans votre code.

0

Si votre compilateur n'est pas assez intelligent pour optimiser la copie locale qui n'est pas modifiée, il n'est probablement pas assez intelligent pour optimiser la référence locale. Dans ce cas, il générera un code encore plus affreux pour le cas du renvoi par référence (car chaque accès est indirect).

0

Une réponse très courte: lorsque vous décidez de passer par référence ou par valeur, traitez les fonctions inline et non-inline de la même manière.

0

Dans le cas de primitives, cela n'a pas d'importance car vous ne passez que 4 octets.

La raison pour laquelle une référence est transmise est la taille de 4 octets, ce qui représente une réduction drastique dans le cas des types personnalisés et des chaînes volumineuses.

L'argument est pour la vitesse ... habituellement.

Dans le cas d'une fonction en ligne, vous voudriez que tous les types qui ne sont pas des primitives soient transmis par référence, puisque vous dites au compilateur de les aligner en premier lieu.

-1

En général,

Déclarez uniquement les primitives de sortie comme références.

déclarer Seule une entrée primitive comme ref référence ou const si vous devez interdire les expressions:

int two = plus1(1); // compile error if plus1 is declared as "int plus1(int&)" 

double y = sqrt(1.1 * 2); // compile error if sqrt is declared as "double sqrt(const double&)" 
+1

Pourquoi pensez-vous que 'double sqrt (const double &)' est incompatible avec 'sqrt (1.1 * 2) '? – 6502

1

passe par la valeur si le type est inférieur ou comparable à un pointeur; par exemple, int, char, double, small structs, ...

Passage par référence pour les objets plus grands; par exemple, les conteneurs STL. J'ai lu beaucoup de choses sur les compilateurs capables de l'optimiser mais ils ne l'ont pas fait à mon simple benchmark qui suit. Sauf si vous voulez perdre du temps à tester des cas d'utilisation, utilisez const T& obj.

Bonus: Pour une utilisation plus rapide vitesse restrict de C99 (cette façon, vous rattrapez Fortran, ce qui limite l'aliasing pointeur, cas d'utilisation. f(const T&__restrict__ obj) C++ standard ne permet pas mot-clé restrict mais les compilateurs utilisent des mots clés internes - g ++ utilise __restrict__ . S'il n'y a pas aliasing dans le code, il n'y a pas de gain de vitesse

la référence avec g ++ 4.9.2.

passage vecteur par référence:

> cat inpoint.cpp 
#include <vector> 
#include <iostream> 

using namespace std; 

inline int show_size(const vector<int> &v) { 
    return v.size(); 
} 

int main(){ 
    vector<int> v(100000000); 
    cout << show_size(v) << endl; 
    return 0; 
} 
> g++ -std=c++14 -O2 inpoint.cpp; time ./a.out 
100000000 

real 0m0.330s 
user 0m0.072s 
sys  0m0.256s 

Passant vecteur par la valeur prend deux fois plus de temps:

> cat invalue.cpp 
#include <vector> 
#include <iostream> 

using namespace std; 

inline int show_size(vector<int> v) { 
    return v.size(); 
} 

int main(){ 
    vector<int> v(100000000); 
    cout << show_size(v) << endl; 
    return 0; 
} 
> g++ -std=c++14 -O2 invalue.cpp; time ./a.out 
100000000 

real 0m0.985s 
user 0m0.204s 
sys  0m0.776s 
Questions connexes