Mam kilka klas wektorowych, w których funkcje arytmetyczne wyglądają tak:
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;
}
Chcę zrobić trochę czyszczenia, aby usunąć zduplikowany kod. Zasadniczo chcę przekonwertować wszystkie operator*
funkcje, aby wywoływały operator*=
funkcje takie jak to:
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;
}
Jestem jednak zaniepokojony, czy nie spowoduje to dodatkowych kosztów związanych z wywołaniem funkcji dodatkowej.
Czy to dobry pomysł? Kiepski pomysł?
c++
mathematics
performance
refactoring
użytkownik112513312
źródło
źródło
*
i*=
robię dwie różne rzeczy - pierwsza dodaje poszczególne wartości, druga zwielokrotnia je. Wydają się również mieć podpisy innego typu.Odpowiedzi:
W praktyce nie będą ponoszone żadne dodatkowe koszty ogólne . W C ++ małe funkcje są zwykle kompilowane przez kompilator jako optymalizacja, więc powstały zestaw będzie miał wszystkie operacje w witrynie wywołań - funkcje nie będą się nawzajem wywoływać, ponieważ funkcje nie będą istnieć w końcowym kodzie, tylko operacje matematyczne.
W zależności od kompilatora jedna z tych funkcji może wywoływać drugą bez optymalizacji lub z niską optymalizacją (jak w przypadku kompilacji debugowania). Jednak na wyższym poziomie optymalizacji (kompilacje wersji) zostaną one zoptymalizowane do samej matematyki.
Jeśli nadal chcesz być pedantyczny (powiedz, że tworzysz bibliotekę), dodanie
inline
słowa kluczowego dooperator*()
(i podobnych funkcji opakowania) może zasugerować kompilatorowi, aby wykonał polecenie wbudowane, lub użyj specyficznych dla kompilatora flag / składni, takich jak:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(kredyt do @Stephane Hockenhull pomocne informacji w komentarzach) . Osobiście staram się podążać za tym, co robią frameworki / biblioteki, których używam - jeśli używam biblioteki matematycznej GLKit, po prostu użyję dostarczonego przez niąGLK_INLINE
makra.Podwójne sprawdzenie za pomocą Clang (Apple LLVM Xcode 7.2 w wersji 7.0.2 / clang-700.1.81) , następująca
main()
funkcja (w połączeniu z twoimi funkcjami i naiwnąVector3<T>
implementacją):kompiluje do tego zestawu przy użyciu flagi optymalizacji
-O0
:Powyżej
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
jest twojaoperator*()
funkcja i kończy się nacallq
innej__…Vector3…
funkcji. Sprowadza się to do całkiem sporego montażu. Kompilacja-O1
jest prawie taka sama, wciąż wywołuje__…Vector3…
funkcje.Jednak kiedy podbijemy go
-O2
,callq
s__…Vector3…
zniknie, zastąpionyimull
instrukcją (* a.z
≈* 3
),addl
instrukcją (* a.y
≈* 2
) i po prostu używającb.x
wartości prosto (ponieważ* a.x
≈* 1
).Dla tego kodu, montaż na
-O2
,-O3
,-Os
, i-Ofast
wszystkie wyglądają identycznie.źródło
inline void foo (const char) __attribute__((always_inline));
). Jeśli chcesz, aby duże obiekty wektorowe działały z rozsądną prędkością, a jednocześnie były debugowalne.addl %edx, %edx
(tj. Dodaje wartość do siebie).