Ich habe einige Vektorklassen, in denen die arithmetischen Funktionen so aussehen:
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;
}
Ich möchte ein bisschen aufräumen, um doppelten Code zu entfernen. Grundsätzlich möchte ich alle operator*
Funktionen konvertieren , um operator*=
Funktionen wie diese aufzurufen :
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;
}
Ich bin jedoch besorgt darüber, ob der zusätzliche Funktionsaufruf zusätzliche Kosten verursacht.
Ist das eine gute Idee? Schlechte Idee?
c++
mathematics
performance
refactoring
user112513312
quelle
quelle
*
und*=
macht zwei verschiedene Dinge - der erste addiert die einzelnen Werte, der zweite multipliziert sie. Sie scheinen auch unterschiedliche Typensignaturen zu haben.Antworten:
In der Praxis fallen keine zusätzlichen Gemeinkosten an . In C ++ werden kleine Funktionen normalerweise vom Compiler als Optimierung eingefügt, sodass die resultierende Assembly alle Operationen auf der aufgerufenen Seite enthält - die Funktionen rufen sich nicht gegenseitig auf, da die Funktionen nur im endgültigen Code nicht vorhanden sind die mathematischen Operationen.
Abhängig vom Compiler wird möglicherweise eine dieser Funktionen angezeigt, die die andere mit keiner oder geringer Optimierung aufruft (wie bei Debugbuilds). Bei höheren Optimierungsstufen (Release-Builds) werden sie jedoch nur auf die Mathematik hin optimiert.
Wenn Sie immer noch umständlich vorgehen möchten (wenn Sie beispielsweise eine Bibliothek erstellen), kann das Hinzufügen des
inline
Schlüsselworts zuoperator*()
(und ähnlichen Wrapper-Funktionen) dazu führen, dass Ihr Compiler die Inline-Funktion ausführt, oder Sie verwenden compilerspezifische Flags / Syntax wie:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(Kredit @Stephane Hockenhull des hilfreichen Informationen in den Kommentaren) . Persönlich neige ich dazu, dem Framework / den Bibliotheken zu folgen, die ich verwende - wenn ich die Mathematikbibliothek von GLKit verwende, verwende ich nur dasGLK_INLINE
Makro, das es auch bereitstellt.Überprüfen Sie mit Clang (Xcode 7.2s Apple LLVM Version 7.0.2 / clang-700.1.81) die folgende
main()
Funktion (in Kombination mit Ihren Funktionen und einer naivenVector3<T>
Implementierung):Kompiliert mit dem Optimierungsflag zu dieser Assembly
-O0
:Oben
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
ist Ihreoperator*()
Funktion und endet mit einercallq
anderen__…Vector3…
Funktion. Es ist ein ziemlicher Montageaufwand. Das Kompilieren mit-O1
ist fast dasselbe und ruft immer noch__…Vector3…
Funktionen auf.Wenn wir es jedoch anstoßen
-O2
, müssen diecallq
s__…Vector3…
verschwinden, durch eineimull
Anweisung (das* a.z
≈* 3
), eineaddl
Anweisung (das* a.y
≈* 2
) und einfach denb.x
Wert straight-up (weil* a.x
≈* 1
) ersetzt werden.Für diesen Code, wobei die Anordnung an
-O2
,-O3
,-Os
, und-Ofast
alle identisch aussehen.quelle
inline void foo (const char) __attribute__((always_inline));
) verwenden. Wenn Sie möchten, dass vektorlastige Dinge mit einer angemessenen Geschwindigkeit ausgeführt werden, während sie noch debuggbar sind.addl %edx, %edx
(dh addiert den Wert zu sich selbst).