2011-01-26 19 views
7

Supongamos que quiero hacer una serie de clases que tengan cada una función de miembro con la misma función. Vamos a llamar a la funciónEvitar funciones virtuales

void doYourJob(); 

quiero poner finalmente todas estas clases en el mismo recipiente de modo que puede recorrer a través de ellos y tienen cada uno realice 'doYourJob()'

La solución obvia es hacer una clase abstracta con la función

virtual void doYourJob(); 

pero estoy indeciso de hacerlo. Este es un programa costoso en el tiempo y una función virtual lo reduciría considerablemente. Además, esta función es lo único que las clases tienen en común entre sí y doYourJob está implícito de manera completamente diferente para cada clase.

¿Hay alguna manera de evitar el uso de una clase abstracta con una función virtual o voy a tener que absorberla?

+1

Impleméntelo utilizando clases polimórficas. Es muy probable que la sobrecarga de enviar llamadas a las funciones virtuales sea insignificante. En el peor de los casos, es importante, y al menos tendrá un diseño limpio que puede optimizar con relativa facilidad. –

Respuesta

6

Las funciones virtuales no cuestan mucho. Son una llamada indirecta, básicamente como un puntero de función. What is the performance cost of having a virtual method in a C++ class?

Si estás en una situación en la que cada ciclo por números de llamadas, que es lo que está haciendo muy poco trabajo en la llamada de función y que está llamando desde su bucle interno en una aplicación crítica de rendimiento es probable necesita un enfoque diferente por completo.

4

Me temo que una serie de comprobaciones dynamic_cast en un bucle reduciría el rendimiento peor que una función virtual. Si vas a tirarlos todos en un contenedor, necesitan tener algún tipo en común, por lo que también puedes convertirlo en una clase base pura virtual con ese método.

No hay mucho para el despacho virtual de funciones en ese contexto: una búsqueda vtable, un ajuste del puntero this suministrado, y una llamada indirecta.

Si el rendimiento es tan crítico, es posible que pueda usar un contenedor separado para cada subtipo y procesar cada contenedor de forma independiente. Si el orden es importante, estarías haciendo tantos backflips que probablemente el despacho virtual sea más rápido.

+3

'dynamic_cast' solo funciona para tipos que tienen al menos una función virtual, y si ese es el caso, es mucho mejor que simplemente use esa función virtual para' doYourJob() '. – templatetypedef

+0

sería tan simple como usar [code] class base_class {virtual void doYourJob() {}} [code] o hay alguna forma de hacerlo más eficiente – hedgehogrider

+1

Realmente es así de simple. Es posible que desee utilizar 'virtual void doYourJob() = 0;' en su lugar, de modo que las subclases ** deben ** proporcionar una implementación en lugar de obtener una implementación no operativa predeterminada. En cualquier caso, el costo es que agrega un puntero al 'sizeof' de cada clase, y tiene que hacer uno o dos indirectos en el tiempo de llamada. Son unos pocos nanosegundos. –

1

Si vas a almacenar todos estos objetos en el mismo contenedor, entonces vas a tener que escribir un tipo de contenedor heterogéneo (lento y caro), vas a tener que almacenar un contenedor de void * s (yuck!), o las clases van a tener que estar relacionadas entre sí a través de la herencia. Si opta por ir con cualquiera de las dos primeras opciones, tendrá que tener un poco de lógica para examinar cada elemento en el contenedor, averiguar qué tipo es, y luego llamar a la implementación correspondiente doYourJob(), que esencialmente hierve hasta la herencia.

Sugiero probar el enfoque simple y directo de usar la herencia primero. Si esto es lo suficientemente rápido, ¡eso es genial! Ya terminaste Si no es así, intente utilizar algún otro esquema. Nunca evite una función de idioma útil debido al costo a menos que tenga alguna buena prueba sólida que sugiera que el costo es demasiado alto.

8

Si necesita la velocidad, considere incrustar un "número de tipo (identificación)" en los objetos, y usar una instrucción de conmutación para seleccionar el código específico del tipo. Esto puede evitar la sobrecarga de llamada de función por completo, solo haciendo un salto local. No vas a llegar más rápido que eso. Un costo (en términos de mantenimiento, dependencias de recompilación, etc.) es forzar la localización (en el conmutador) de la funcionalidad específica del tipo.


APLICACIÓN

#include <iostream> 
#include <vector> 

// virtual dispatch model... 

struct Base 
{ 
    virtual int f() const { return 1; } 
}; 

struct Derived : Base 
{ 
    virtual int f() const { return 2; } 
}; 

// alternative: member variable encodes runtime type... 

struct Type 
{ 
    Type(int type) : type_(type) { } 
    int type_; 
}; 

struct A : Type 
{ 
    A() : Type(1) { } 
    int f() const { return 1; } 
}; 

struct B : Type 
{ 
    B() : Type(2) { } 
    int f() const { return 2; } 
}; 

struct Timer 
{ 
    Timer() { clock_gettime(CLOCK_MONOTONIC, &from); } 
    struct timespec from; 
    double elapsed() const 
    { 
     struct timespec to; 
     clock_gettime(CLOCK_MONOTONIC, &to); 
     return to.tv_sec - from.tv_sec + 1E-9 * (to.tv_nsec - from.tv_nsec); 
    } 
}; 

int main(int argc) 
{ 
    for (int j = 0; j < 3; ++j) 
    { 
    typedef std::vector<Base*> V; 
    V v; 

    for (int i = 0; i < 1000; ++i) 
     v.push_back(i % 2 ? new Base : (Base*)new Derived); 

    int total = 0; 

    Timer tv; 

    for (int i = 0; i < 100000; ++i) 
     for (V::const_iterator i = v.begin(); i != v.end(); ++i) 
      total += (*i)->f(); 

    double tve = tv.elapsed(); 

    std::cout << "virtual dispatch: " << total << ' ' << tve << '\n'; 

    // ---------------------------- 

    typedef std::vector<Type*> W; 
    W w; 

    for (int i = 0; i < 1000; ++i) 
     w.push_back(i % 2 ? (Type*)new A : (Type*)new B); 

    total = 0; 

    Timer tw; 

    for (int i = 0; i < 100000; ++i) 
     for (W::const_iterator i = w.begin(); i != w.end(); ++i) 
     { 
      if ((*i)->type_ == 1) 
       total += ((A*)(*i))->f(); 
      else 
       total += ((B*)(*i))->f(); 
     } 

    double twe = tw.elapsed(); 

    std::cout << "switched: " << total << ' ' << twe << '\n'; 

    // ---------------------------- 

    total = 0; 

    Timer tw2; 

    for (int i = 0; i < 100000; ++i) 
     for (W::const_iterator i = w.begin(); i != w.end(); ++i) 
      total += (*i)->type_; 

    double tw2e = tw2.elapsed(); 

    std::cout << "overheads: " << total << ' ' << tw2e << '\n'; 
    } 
} 

RESULTADOS DE RENDIMIENTO

En mi sistema Linux:

~/dev g++ -O2 -o vdt vdt.cc -lrt 
~/dev ./vdt      
virtual dispatch: 150000000 1.28025 
switched: 150000000 0.344314 
overhead: 150000000 0.229018 
virtual dispatch: 150000000 1.285 
switched: 150000000 0.345367 
overhead: 150000000 0.231051 
virtual dispatch: 150000000 1.28969 
switched: 150000000 0.345876 
overhead: 150000000 0.230726 

Esto sugiere que un enfoque en línea de tipo de número conmutado es de aproximadamente (1.28 - 0.23)/(0.344 - 0.23) = 9.2 veces más rápido. Por supuesto, eso es específico del sistema exacto probado/indicador de compiladores & versión etc., pero generalmente indicativo.


COMENTARIOS RE DESPACHO VIRTUAL

Hay que decir sin embargo que los gastos generales de llamada de función virtual son algo que rara vez es significativa, y sólo por algunas veces denominadas funciones triviales (como captadores y definidores). Incluso entonces, es posible que pueda proporcionar una sola función para obtener y configurar un montón de cosas a la vez, lo que minimiza el costo. A la gente le preocupa mucho el envío virtual, así que haz el perfil antes de encontrar alternativas incómodas. El problema principal con ellos es que realizan una llamada de función fuera de línea, aunque también deslocalizan el código ejecutado que cambia los patrones de utilización de la memoria caché (para mejor o (más a menudo) peor).

+0

¿por qué el código es mucho más rápido con '-lrt' con clang ++ y sin? http://coliru.stacked-crooked.com/a/83ac0dc7d15b4747 – Gabriel

+0

¿Cuál es la bandera '-lrt', vínculo con la biblioteca" rt "?? – Gabriel

+0

@Gabriel: sí - vinculará librt.so - "rt" significa en tiempo real, y contiene la función 'clock_gettime'. –

Cuestiones relacionadas