Eu tenho algumas classes de vetores onde as funções aritméticas se parecem com isso:
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;
}
Quero fazer um pouco de limpeza para remover o código duplicado. Basicamente, quero converter todas as operator*
funções para chamar operator*=
funções como esta:
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;
}
Mas estou preocupado em saber se haverá alguma sobrecarga adicional com a chamada de função extra.
É uma boa ideia? Péssima ideia?
c++
mathematics
performance
refactoring
user112513312
fonte
fonte
*
e*=
está fazendo duas coisas diferentes - a primeira adiciona os valores individuais e a segunda os multiplica. Eles também parecem ter assinaturas de tipo diferentes.Respostas:
Na prática, nenhuma sobrecarga adicional será incorrida . No C ++, as pequenas funções geralmente são incorporadas pelo compilador como uma otimização, de modo que o assembly resultante terá todas as operações no local da chamada - as funções não se chamarão, pois as funções não existirão no código final, apenas as operações matemáticas.
Dependendo do compilador, você pode ver uma dessas funções chamando a outra sem ou com baixa otimização (como nas compilações de depuração). Porém, em um nível de otimização mais alto (compilações de versão), elas serão otimizadas apenas para a matemática.
Se você ainda quiser ser pedante (digamos que esteja criando uma biblioteca), adicionar a
inline
palavra-chave aoperator*()
(e funções semelhantes do wrapper) pode sugerir ao seu compilador que execute o inline ou use sinalizadores / sintaxe específicos do compilador, como:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(crédito para informações úteis de @Stephane Hockenhull nos comentários) . Pessoalmente, tenho a tendência de seguir o que as estruturas / bibliotecas que estou usando - se estiver usando a biblioteca de matemática do GLKit, também usarei aGLK_INLINE
macro que ele fornece.Verifique novamente usando o Clang (Apple LLVM versão 7.0.2 / clang-700.1.81) do Xcode 7.2 , a seguinte
main()
função (em combinação com suas funções e umaVector3<T>
implementação ingênua ):compila esse assembly usando o sinalizador de otimização
-O0
:No exemplo acima,
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
é suaoperator*()
função e acaba comcallq
outra__…Vector3…
função. Isso equivale a bastante montagem. Compilar com-O1
é quase o mesmo, ainda chamando para__…Vector3…
funções.No entanto, quando aumentamos
-O2
, oscallq
s__…Vector3…
desaparecem, substituídos por umaimull
instrução (o* a.z
≈* 3
), umaaddl
instrução (o* a.y
≈* 2
) e apenas usando ob.x
valor diretamente (porque* a.x
≈* 1
).Para este código, a montagem em
-O2
,-O3
,-Os
, e-Ofast
todos parecem idênticos.fonte
inline void foo (const char) __attribute__((always_inline));
). Se você deseja que coisas pesadas em vetor sejam executadas a uma velocidade razoável enquanto ainda são depuráveis.addl %edx, %edx
(ou seja, agrega o valor a si mesma).