J'ai quelques classes vectorielles où les fonctions arithmétiques ressemblent à ceci:
template<typename T, typename U>
auto operator*(const Vector3<T>& lhs, const Vector3<U>& rhs)
{
return Vector3<decltype(lhs.x*rhs.x)>(
lhs.x + rhs.x,
lhs.y + rhs.y,
lhs.z + rhs.z
);
}
template<typename T, typename U>
Vector3<T>& operator*=(Vector3<T>& lhs, const Vector3<U>& rhs)
{
lhs.x *= rhs.x;
lhs.y *= rhs.y;
lhs.z *= rhs.z;
return lhs;
}
Je veux faire un peu de nettoyage pour supprimer le code dupliqué. Fondamentalement, je veux convertir toutes les operator*
fonctions pour appeler des operator*=
fonctions comme ceci:
template<typename T, typename U>
auto operator*(const Vector3<T>& lhs, const Vector3<U>& rhs)
{
Vector3<decltype(lhs.x*rhs.x)> result = lhs;
result *= rhs;
return result;
}
Mais je m'inquiète de savoir si cela entraînera des frais supplémentaires de l'appel de fonction supplémentaire.
Est-ce que c'est une bonne idée? Mauvaise idée?
c++
mathematics
performance
refactoring
user112513312
la source
la source
*
et*=
fait deux choses différentes - la première ajoute les valeurs individuelles, la seconde les multipliant. Ils semblent également avoir différents types de signatures.Réponses:
En pratique, aucun frais supplémentaire ne sera engagé . En C ++, les petites fonctions sont généralement intégrées par le compilateur comme une optimisation, donc l'assembly résultant aura toutes les opérations sur le site d'appel - les fonctions ne s'appelleront pas, car les fonctions n'existeront pas dans le code final, seulement les opérations mathématiques.
Selon le compilateur, vous pouvez voir l'une de ces fonctions appeler l'autre avec une optimisation nulle ou faible (comme avec les versions de débogage). Cependant, à un niveau d'optimisation plus élevé (versions de version), elles seront optimisées jusqu'au niveau mathématique.
Si vous souhaitez toujours être pédant à ce sujet (par exemple, vous créez une bibliothèque), l'ajout du
inline
mot clé àoperator*()
(et des fonctions d'encapsulation similaires) peut suggérer à votre compilateur d'effectuer l'inline, ou en utilisant des drapeaux / syntaxe spécifiques au compilateur comme:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(crédit à des informations utiles de @Stephane Hockenhull dans les commentaires) . Personnellement, j'ai tendance à suivre ce que font les frameworks / bibliothèques que j'utilise - si j'utilise la bibliothèque mathématique de GLKit, je vais simplement utiliser laGLK_INLINE
macro qu'elle fournit également.Double vérification en utilisant Clang (Apple LLVM version 7.0.2 / clang-700.1.81 de Xcode 7.2) , la
main()
fonction suivante (en combinaison avec vos fonctions et uneVector3<T>
implémentation naïve ):compile dans cet assembly en utilisant l'indicateur d'optimisation
-O0
:Dans ce qui précède,
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
est votreoperator*()
fonction et finit parcallq
une autre__…Vector3…
fonction. Cela représente beaucoup de montage. Compiler avec-O1
est presque le même, toujours appeler des__…Vector3…
fonctions.Cependant, lorsque nous le renforçons
-O2
, lecallq
s__…Vector3…
disparaît, remplacé par uneimull
instruction (le* a.z
≈* 3
), uneaddl
instruction (le* a.y
≈* 2
), et en utilisant simplement lab.x
valeur directement (parce que* a.x
≈* 1
).Pour ce code, l'assemblée à
-O2
,-O3
,-Os
, et-Ofast
tous semblent identiques.la source
inline void foo (const char) __attribute__((always_inline));
). Si vous voulez que les choses lourdes de vecteurs fonctionnent à une vitesse raisonnable tout en étant déboguables.addl %edx, %edx
(c'est- à -dire ajouter la valeur à elle-même).