Tengo algunas clases de vectores donde las funciones aritméticas se ven así:
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;
}
Quiero hacer un poco de limpieza para eliminar el código duplicado. Básicamente, quiero convertir todas las operator*
funciones para llamar a operator*=
funciones 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;
}
Pero me preocupa si incurrirá en una sobrecarga adicional de la llamada de función adicional.
¿Es una buena idea? ¿Mala idea?
c++
mathematics
performance
refactoring
usuario112513312
fuente
fuente
*
y*=
está haciendo dos cosas diferentes: la primera agrega los valores individuales, la segunda los multiplica. También parecen tener diferentes tipos de firmas.Respuestas:
En la práctica, no se incurrirá en gastos generales adicionales . En C ++, las funciones pequeñas generalmente están alineadas por el compilador como una optimización, por lo que el ensamblado resultante tendrá todas las operaciones en el sitio de llamadas: las funciones no se llamarán entre sí, ya que las funciones no existirán en el código final, solo Las operaciones matemáticas.
Dependiendo del compilador, puede ver que una de estas funciones llama a la otra sin optimización o con poca optimización (como con las compilaciones de depuración). Sin embargo, a un nivel de optimizaciones más alto (versiones de lanzamiento), se optimizarán solo para las matemáticas.
Si aún desea ser pedante al respecto (digamos que está creando una biblioteca), agregar la
inline
palabra claveoperator*()
(y funciones de envoltura similares) puede sugerirle a su compilador que realice la línea, o usar indicadores / sintaxis específicos del compilador como:-finline-small-functions
,-finline-functions
,-findirect-inlining
,__attribute__((always_inline))
(crédito a información útil de @Stephane Hockenhull en los comentarios) . Personalmente, tiendo a seguir lo que hacen el framework / libs que estoy usando; si estoy usando la biblioteca matemática de GLKit, solo usaré laGLK_INLINE
macro que proporciona también.Verificación doble usando Clang (Apple LLVM Xcode 7.2 versión 7.0.2 / clang-700.1.81) , la siguiente
main()
función (en combinación con sus funciones y unaVector3<T>
implementación ingenua ):compila a este ensamblaje utilizando el indicador de optimización
-O0
:En lo anterior,
__ZmlIiiE7Vector3IDTmldtfp_1xdtfp0_1xEERKS0_IT_ERKS0_IT0_E
es suoperator*()
función y terminacallq
con otra__…Vector3…
función. Se trata de una gran cantidad de montaje. Compilar con-O1
es casi lo mismo, aún llamando a las__…Vector3…
funciones.Sin embargo, cuando lo subimos
-O2
, lacallq
s__…Vector3…
desaparece, reemplazada por unaimull
instrucción (la* a.z
≈* 3
), unaaddl
instrucción (la* a.y
≈* 2
), y simplemente usando elb.x
valor directamente (porque* a.x
≈* 1
).Para que este código, la asamblea en
-O2
,-O3
,-Os
, y-Ofast
toda la mirada idéntica.fuente
inline void foo (const char) __attribute__((always_inline));
). Si desea que las cosas con muchos vectores se ejecuten a una velocidad razonable mientras aún se pueden depurar.addl %edx, %edx
(es decir, agregar el valor a sí mismo).