Pregunta

Tengo una biblioteca en C con numerosas rutinas matemáticas para tratar con vectores, matrices, cuaterniones, etc. Debe permanecer en C porque a menudo lo uso para trabajos integrados y como una extensión Lua. Además, tengo envoltorios de clase C ++ para permitir una administración de objetos más conveniente y una sobrecarga del operador para operaciones matemáticas usando la API de C. El reiniciador solo consiste en un archivo de encabezado y se hace el mayor uso posible en la alineación.

¿Existe una penalidad apreciable por envolver el código C frente a portar e incluir la implementación directamente en la clase C ++? Esta biblioteca se usa en aplicaciones de tiempo crítico. Entonces, ¿el impulso de eliminar la indirección compensa el dolor de cabeza de mantenimiento de dos puertos?

Ejemplo de interfaz C:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

Ejemplo de contenedor C ++:

class Vector3
{
private:
    VECTOR3 v_;

public:
    // copy constructors, etc...

    Vector3& operator+=(const Vector3& rhs)
    {
        v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
        return *this;
    }

    Vector3 operator+(const Vector3& rhs) const
    {
        Vector3 tmp(*this);
        tmp += rhs;
        return tmp;
    }

    // more methods...
};
¿Fue útil?

Solución

Su contenedor en sí estará en línea, sin embargo, sus llamadas de método a la biblioteca C generalmente no lo estarán. (Esto requeriría optimizaciones de tiempo de enlace que son técnicamente posibles, pero AFAIK rudimentario en el mejor de los casos en las herramientas actuales)

Generalmente, una llamada de función como tal no es muy costosa. El costo del ciclo ha disminuido considerablemente en los últimos años, y se puede predecir fácilmente, por lo que la penalización de la llamada como tal es insignificante.

Sin embargo, la inclusión en línea abre la puerta a más optimizaciones: si tiene v = a + b + c, su clase contenedora fuerza la generación de variables de pila, mientras que para las llamadas en línea, la mayoría de los datos se pueden guardar en la FPU apilar. Además, el código en línea permite simplificar las instrucciones, considerando valores constantes y más.

Entonces, si bien la regla antes de invertir es cierta, esperaría un margen de mejora aquí.


Una solución típica es llevar la implementación de C a un formato que pueda usarse como funciones en línea o como "C". cuerpo:

// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
    // here you maintain the actual implementations
    // ...
}

// C header
#define V3DECL 
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

// C body
#include "V3impl.inl"


// CPP Header
#define V3DECL inline
namespace v3core {
  #include "V3impl.inl"
} // namespace

class Vector3D { ... }

Esto probablemente tiene sentido solo para métodos seleccionados con cuerpos comparativamente simples. Movería los métodos a un espacio de nombres separado para la implementación de C ++, ya que generalmente no los necesitará directamente.

(Tenga en cuenta que la línea es solo una sugerencia del compilador, no obliga a que el método esté en línea. Pero eso es bueno: si el tamaño del código de un bucle interno excede el caché de instrucciones, la alineación perjudica fácilmente el rendimiento)

Si la aprobación / devolución por referencia puede resolverse depende de la fortaleza de su compilador, he visto muchas     foo (X * fuera) fuerza las variables de pila, mientras que     X foo () mantiene los valores en los registros.

Otros consejos

Si solo está ajustando las llamadas de la biblioteca C en funciones de clase C ++ (en otras palabras, las funciones C ++ no hacen más que llamar a funciones C), entonces el compilador optimizará estas llamadas para que no sea una penalización de rendimiento.

Como con cualquier pregunta sobre el rendimiento, se le pedirá que mida para obtener su respuesta (y esa es la respuesta estrictamente correcta).

Pero, como regla general, para los métodos en línea simples que realmente se pueden alinear, no verá una penalización de rendimiento. En general, un método en línea que no hace más que pasar la llamada a otra función es un gran candidato para la inclusión en línea.

Sin embargo, incluso si sus métodos de envoltura no estuvieran alineados, sospecho que no notará ninguna penalización de rendimiento, ni siquiera una cuantificable, a menos que el método de envoltura se haya llamado en algún bucle crítico. Incluso entonces, probablemente solo sería medible si la función envuelta en sí misma no hiciera mucho trabajo.

Este tipo de cosas es lo último que debe preocuparse. Primero, preocúpese por hacer que su código sea correcto, mantenible y que esté utilizando los algoritmos apropiados.

Como es habitual con todo lo relacionado con la optimización, la respuesta es que debe medir el rendimiento en sí mismo antes de saber si la optimización vale la pena.

  • Compare dos funciones diferentes, una llamando directamente a las funciones de estilo C y otra llamando a través del contenedor. Vea cuál funciona más rápido o si la diferencia está dentro del margen de error de su medición (lo que significaría que no hay diferencia que pueda medir).
  • Observe el código de ensamblaje generado por las dos funciones en el paso anterior (en gcc, use -S o -save-temps ). Vea si el compilador hizo algo estúpido, o si sus contenedores tienen algún error de rendimiento.

A menos que la diferencia de rendimiento sea demasiado grande a favor de no usar el envoltorio, la reimplementación no es una buena idea, ya que corre el riesgo de introducir errores (que incluso podrían causar resultados que parecen correctos pero están mal). Incluso si la diferencia es grande, sería más simple y menos arriesgado recordar que C ++ es muy compatible con C y usar su biblioteca en el estilo C incluso dentro del código de C ++.

No creo que note mucha diferencia de rendimiento. Suponiendo que su plataforma de destino admita todos sus tipos de datos,

Estoy codificando el DS y algunos otros dispositivos ARM y los puntos flotantes son malos ... Tuve que escribir def flotante en FixedPoint < 16,8 >

Si le preocupa que la sobrecarga de las funciones de llamada lo esté ralentizando, ¿por qué no prueba incluir el código C o convertirlo en macros?

Además, ¿por qué no mejorar la corrección constante del código C mientras lo hace? Const_cast realmente debe usarse con moderación, especialmente en las interfaces que controla.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top