2012-02-28 9 views
10

Recientemente me dijeron en una revisión de código (por un desarrollador de C++ más viejo y más sabio) para reescribir una clase que había escrito convirtiéndola en un conjunto de métodos estáticos. Él justificó esto diciendo que aunque mi objeto contenía una muy pequeña cantidad de estado interno, podría derivarse en tiempo de ejecución de todos modos y si cambiara a métodos estáticos, evitaría el costo de insanar objetos por todas partes.¿Hay un costo inherente significativo de la instanciación de objetos en C++?

He realizado este cambio pero me hizo pensar, ¿cuál es el costo de creación de instancias en C++? Soy consciente de que en los lenguajes administrados, todo el costo de la recolección de basura es significativo. Sin embargo, mi objeto C++ estaba simplemente en la pila, no contenía ningún método virtual, por lo que no habría costo de búsqueda de la función de tiempo de ejecución. Utilicé el nuevo mecanismo de eliminación de C++ 11 para eliminar los operadores de copia/asignación predeterminados, por lo que no hubo copia involucrada. Era simplemente un objeto simple con un constructor que hacía una pequeña cantidad de trabajo (requerido de todos modos con métodos estáticos) y un destructor que no hacía nada. ¿Puede decirme de todos modos qué serían estas constelaciones de instancias? (El revisor es un poco intimidante y no quiero parecer estúpido preguntándole!) ;-)

+3

suena muy dudosa para mí, y, a menos que se está ejecutando en algún punto de acceso rendimiento, completamente irrelevante. – spencercw

+2

Nunca te ves estúpido por hacer preguntas. Si él piensa que eres entonces ÉL ES ESTÚPIDO. Preguntar es cómo nos hacemos mejor. – Iznogood

+0

@spencercw: ¿Pero por qué hacer que una función sea una función miembro si puede ser estática? Incluso desde una perspectiva de diseño que no tiene sentido. –

Respuesta

3

Probablemente no mucho, y me sorprendería si fuera algún tipo de cuello de botella. Pero existe el principio de la cosa si nada más.

Sin embargo, se debe preguntarle a la persona; nunca tengas miedo de hacer eso, y no está del todo claro aquí que perder el estado almacenado y, en cambio, derivarlo cada vez (si eso es lo que estás haciendo en su lugar) no va a empeorar las cosas . Y, si no lo es, pensarías que un espacio de nombres sería mejor que los métodos estáticos.

Un caso/ejemplo de prueba haría que esto fuera más fácil de responder categóricamente, más allá de "deberías preguntarle".

6

Si su tipo de objeto debe invocar un destructor y un constructor no triviales durante su ciclo de vida, entonces el costo será el mínimo para crear cualquier objeto C++ que tenga un destructor y constructor no trivial. Hacer el resto de sus métodos static no reducirá ese costo. El "precio" del espacio será de al menos 1 byte dado que su clase no es una clase base de una clase derivada, y el único ahorro de costos en las llamadas al método de clase static será la omisión del puntero implícito this pasado como el primer argumento oculto de la llamada, algo que se requeriría para los métodos de clase no estáticos.

Si los métodos de su revisor le está pidiendo que re-designado como static nunca toque la ficha miembros no estáticos de su tipo de clase, entonces el paso de la this puntero implícito es un recurso desperdiciado, y el revisor tiene un buen punto. De lo contrario, tendría que agregar un argumento a los métodos estáticos que tomarían el tipo de clase como referencia o puntero, anulando el rendimiento obtenido de la omisión del puntero implícito this.

+2

Creo que la idea es que la clase sería desinstalable; es decir, él realmente debería estar usando un espacio de nombres en su lugar. –

+0

@LightnessRacesinOrbit: Sí, si es * solo * no se necesita un conjunto de funciones estáticas que la clase, pero el OP dijo que la clase de hecho mantenía cantidades de estado "mínimas". –

+0

@EdS .: ... y que se sugirió que ese estado podría derivarse en caso necesario –

2

Como regla general, si una función puede convertirse estática, probablemente debería serlo. Es más barato. ¿Cuánto más barato? Eso depende de lo que haga el objeto en su constructor, pero el costo base de construir un objeto C++ no es tan alto (la asignación de memoria dinámica, por supuesto, es más costosa).

El punto es no pagar por lo que no necesita. Si una función puede ser estática, ¿por qué convertirla en una función miembro? No tiene sentido ser una función miembro en ese caso. ¿La pena de crear un objeto matará el rendimiento de tu aplicación? Probablemente no, pero de nuevo, ¿por qué pagar por lo que no necesitas?

+0

requieren 'operator()' para ser miembro, pero es más rápido estático. Todavía tiene que ser miembro en ese caso. –

3

Depende de lo que hace su aplicación.¿Es un sistema de tiempo real en un dispositivo con memoria limitada? De lo contrario, la mayoría de las veces la instanciación de objetos no será un problema, a menos que estés instanciando millones de estos y manteniéndolos cerca o algún diseño extraño como ese. mayoría sistemas tendrán mucho más cuellos de botella, tales como:

  • entrada del usuario
  • red llama
  • el acceso de base de datos
  • cálculo algos intensivos
  • conmutación hilo costes
  • llamadas al sistema

Creo que en la mayoría de los casos encap sulation en una clase para el diseño supera a los pequeños costos de creación de instancias. Por supuesto, puede haber un 1% de casos en que esto no sea válido, pero ¿es suyo el tuyo?

7

Respuesta corta - la asignación intrínseca de objetos es barata, pero puede ser costosa en ciertos casos.

Respuesta larga

en C++ el costo de crear instancias de un objeto es lo mismo que crear instancias de una estructura en C. Todo es un objeto, es un bloque de memoria lo suficientemente grande para almacenar la tabla v (si tiene uno) y todos los atributos de datos. Los métodos no consumen más memoria después de que se haya instanciado v-table.

Un método no virtual es una función simple con un implícito this como primer parámetro. Llamar a una función virtual es un poco más complicado ya que se debe realizar una búsqueda en la tabla v para saber qué función de qué clase llamar.

Esto significa que crear instancias de un objeto en la pila implica una simple disminución del puntero de la pila (para una pila descendente completa).

Cuando se crea una instancia de un objeto en el montón, el costo puede aumentar sustancialmente. Pero esto es algo inherente a cualquier asignación relacionada con el montón. Al asignar memoria en el montón, el montón necesita encontrar un bloque libre lo suficientemente grande como para contener su objeto. Encontrar dicho bloque es una operación de tiempo no constante y puede ser costoso.

C++ tiene constructores que pueden asignar más memoria para ciertos atributos de datos de puntero. Estos son normalmente asignados en el montón. Esto se complica aún más si dichos miembros de datos realizan asignaciones de montón ellos mismos. Esto puede llevar a algo que involucre una cantidad sustancial de instrucciones.

El resultado final es que depende de cómo y qué objeto está instalando.

+0

Esto es más o menos lo que entendí que era el caso. En mi caso, como dije en mi pregunta, no hay vtables para tratar y no hay asignación de memoria dinámica. Entonces, realmente, el único costo extra es el implícito que pasa este puntero. Lo cual, dado que el código no es crítico para el desempeño, probablemente significa que el cambio ni siquiera se podrá medir en términos de rendimiento. Sin embargo, probablemente sea lo correcto desde un punto de vista de estilo/diseño. – Benj

1

Como otros han sugerido hablar con su colega y pedirle que explique su razonamiento. Si es práctico, debe investigar con un pequeño programa de prueba el rendimiento de las dos versiones. Hacer ambas cosas te ayudará a crecer como programador.

En general, estoy de acuerdo con los consejos para hacer que la función miembro sea estática si es práctica. No por razones de rendimiento, sino porque reduce la cantidad de contexto que necesita recordar para comprender el comportamiento de la función.

Vale la pena señalar que hay un caso en el que el uso de una función miembro dará como resultado un código más rápido. Ese caso es cuando el compilador puede realizar la alineación.Este es un tipo de tema avanzado pero es algo así que dificulta escribir reglas categóricas sobre programación.

#include <algorithm> 
#include <iostream> 
#include <vector> 
#include <stdlib.h> 
#include <time.h> 

bool int_lt(int a, int b) 
{ 
    return a < b; 
} 

int 
main() 
{ 
    size_t const N = 50000000; 
    std::vector<int> c1; 
    c1.reserve(N); 
    for (size_t i = 0; i < N; ++i) { 
     int r = rand(); 
     c1.push_back(r); 
    } 
    std::vector<int> c2 = c1; 
    std::vector<int> c3 = c1; 

    clock_t t1 = clock(); 
    std::sort(c2.begin(), c2.end(), std::less<int>()); 
    clock_t t2 = clock(); 
    std::sort(c3.begin(), c3.end(), int_lt); 
    clock_t t3 = clock(); 

    std::cerr << (t2 - t1)/double(CLOCKS_PER_SEC) << '\n'; 
    std::cerr << (t3 - t2)/double(CLOCKS_PER_SEC) << '\n'; 

    return 0; 
} 

En mi i7 Linux porque g ++ no puede inline la función int_lt pero puede inline std :: :: menos operador() la versión no es miembro de la función alrededor del 50% más lento.

> g++-4.5 -O2 p3.cc 
> ./a.out 
3.85 
5.88 

Para entender por qué una diferencia tan grande, debe tener en cuenta qué tipo de infiere el compilador para el comparador. En el caso int_lt infiere el tipo bool (*) (int, int) mientras que con std :: less infiere std :: less. Con el puntero de función, la función a la que se llama solo se conoce en tiempo de ejecución. Lo que significa que es imposible para el compilador alinear su definición en tiempo de compilación. A diferencia de std :: less, el compilador tiene acceso al tipo y su definición en tiempo de compilación, de modo que puede alinear std :: less :: operator(). Lo que hace una diferencia significativa en el rendimiento en este caso.

¿Este comportamiento solo está relacionado con las plantillas? No, se relaciona con una pérdida de abstracción al pasar funciones como objetos. Un puntero de función no incluye tanta información como un tipo de objeto de función para que el compilador use. Aquí hay un ejemplo similar que no usa plantillas (aparte de std :: vector para mayor comodidad).

#include <iostream> 
#include <time.h> 
#include <vector> 
#include <stdlib.h> 

typedef long (*fp_t)(long, long); 

inline long add(long a, long b) 
{ 
    return a + b; 
} 

struct add_fn { 
    long operator()(long a, long b) const 
    { 
     return a + b; 
    } 
}; 

long f(std::vector<long> const& x, fp_t const add, long init) 
{ 
    for (size_t i = 0, sz = x.size(); i < sz; ++i) 
     init = add(init, x[i]); 
    return init;   
} 

long g(std::vector<long> const& x, add_fn const add, long init) 
{ 
    for (size_t i = 0, sz = x.size(); i < sz; ++i) 
     init = add(init, x[i]); 
    return init;   
} 

int 
main() 
{ 
    size_t const N = 5000000; 
    size_t const M = 100; 
    std::vector<long> c1; 
    c1.reserve(N); 
    for (size_t i = 0; i < N; ++i) { 
     long r = rand(); 
     c1.push_back(r); 
    } 
    std::vector<long> c2 = c1; 
    std::vector<long> c3 = c1; 

    clock_t t1 = clock(); 
    for (size_t i = 0; i < M; ++i) 
     long s2 = f(c2, add, 0); 
    clock_t t2 = clock(); 
    for (size_t i = 0; i < M; ++i) 
     long s3 = g(c3, add_fn(), 0); 
    clock_t t3 = clock(); 

    std::cerr << (t2 - t1)/double(CLOCKS_PER_SEC) << '\n'; 
    std::cerr << (t3 - t2)/double(CLOCKS_PER_SEC) << '\n'; 
    return 0; 
} 

Las pruebas de comprobación indican que la función gratuita es 100% más lenta que la función miembro.

> g++ -O2 p5.cc 
> ./a.out 
0.87 
0.32 

Bjarne Stroustrup brindó una excelente conferencia recientemente sobre C++ 11 que trata sobre esto. Puedes verlo en el siguiente enlace.

http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style

+1

Hmm, era consciente de que las funciones de comparador en línea podían afectar drásticamente el rendimiento del género, etc., pero no sabía que un funtor tenía más probabilidades de estar en línea que una función libre. ¿Puedes explicar por qué es esto? – Benj

+1

Hmm, parece que esta candidatura en línea se relaciona más con el uso de plantillas que con el uso de funciones miembro. Buena explicación aquí: http://stackoverflow.com/questions/8925177/c-templates-for-performance – Benj

+0

Benj no se trata de plantillas sino de niveles de abstracción con los que el compilador puede y no puede trabajar. Por favor, avíseme si puedo proporcionar más aclaraciones. También ver la charla de Bjarne Stroustrup es muy interesante. –

Cuestiones relacionadas