У меня есть несколько векторных классов, где арифметические функции выглядят так:
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;
}
Я хочу сделать небольшую очистку, чтобы удалить дублированный код. По сути, я хочу преобразовать все operator*
функции для вызова operator*=
функций следующим образом:
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;
}
Но я обеспокоен тем, не повлечет ли это какие-либо дополнительные издержки от дополнительного вызова функции.
Это хорошая идея? Плохая идея?
c++
mathematics
performance
refactoring
user112513312
источник
источник
*
и*=
делают разные вещи - бывшие добавляют отдельные значения, последних их умножения. Они также имеют подписи разных типов.Ответы:
На практике никаких дополнительных накладных расходов не возникает . В C ++ небольшие функции обычно указываются компилятором как оптимизация, поэтому итоговая сборка будет иметь все операции на месте вызова - функции не будут вызывать друг друга, так как функции не будут существовать в конечном коде, только математические операции.
В зависимости от компилятора, вы можете увидеть, что одна из этих функций вызывает другую без оптимизации или с низкой оптимизацией (как в отладочных сборках). При более высоком уровне оптимизации (сборка релиза) они будут оптимизированы вплоть до математики.
Если вы все еще хотите быть педантичными (скажем, вы создаете библиотеку), добавление
inline
ключевого словаoperator*()
(и аналогичных функций-оболочек) может подсказать вашему компилятору выполнение встроенного кода или использование специфичных для компилятора флагов / синтаксиса, например:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(кредит полезной информации @Stephane Hockenhull в комментариях) . Лично я склонен следовать тому, что делают фреймворки / библиотеки, которые я использую - если я использую математическую библиотеку GLKit, я просто используюGLK_INLINE
макрос, который она предоставляет.Двойная проверка с использованием Clang (Apple LLVM версии 7.0.2 / clang-700.1.81) в Xcode 7.2 , следующей
main()
функции (в сочетании с вашими функциями и простойVector3<T>
реализацией):компилируется в эту сборку с использованием флага оптимизации
-O0
:В приведенном выше,
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
вашаoperator*()
функция и в конечном итогеcallq
другой__…Vector3…
функцией. Это довольно много сборок. Компиляция с-O1
почти такой же, все еще вызывая__…Vector3…
функции.Однако, когда мы поднять его до
-O2
, тоcallq
с до__…Vector3…
исчезнуть, сменившись сimull
инструкцией (* a.z
≈* 3
), сaddl
инструкцией (* a.y
≈* 2
), а просто используяb.x
значение прямо вверх (потому что* a.x
≈* 1
).Для этого кода, сборка на
-O2
,-O3
,-Os
, и-Ofast
все выглядят одинаково.источник
inline void foo (const char) __attribute__((always_inline));
). Если вы хотите, чтобы тяжелые объекты работали с разумной скоростью, но все еще можно было отлаживать.addl %edx, %edx
(т.е. добавляет значение к себе).