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).
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. –