2010-11-06 7 views
8

Sé que el polimorfismo puede agregar una carga perceptible. Llamar a una función virtual es más lento que llamar a una función no virtual. (Toda mi experiencia es sobre GCC, pero creo/escuché que esto es cierto para cualquier compilador real).C++: sobrecarga de polimorfismo de lucha

Muchas veces se llama a una función virtual dada en el mismo objeto una y otra vez; Yo sé que tipo de objeto no cambia, y la mayoría de veces el compilador podría fácilmente deducir que tiene así:

BaseType &obj = ...; 
while(looping) 
    obj.f(); // BaseType::f is virtual 

Para acelerar el código que pude volver a escribir el código anterior como esto:

BaseType &obj = ...; 
FinalType &fo = dynamic_cast< FinalType& >(obj); 
while(looping) 
    fo.f(); // FinalType::f is not virtual 

Me pregunto cuál es la mejor manera de evitar esta sobrecarga debido al polimorfismo en estos casos.

La idea del colado superior (como se muestra en el segundo fragmento) no me parece tan buena: BaseType podría ser heredada por muchas clases, y tratar de lanzar hacia arriba a todas ellas sería bastante prolijo.

Otra idea podría ser la de almacenar obj.f en un puntero de función (no lo prueba, no estoy seguro de que mate la sobrecarga en tiempo de ejecución), pero de nuevo este método no se ve perfecto: como el método anterior, requeriría escribir más código y no sería capaz de explotar algunas optimizaciones (p. ej .: si FinalType::f fuera una función en línea, no se insertaría en línea, pero supongo que la única forma de evitar esto sería con el casting superior obj a su tipo final ...)

Entonces, ¿hay algún método mejor?

Edit: Bueno, por supuesto esto no va a afectar tanto. Esta pregunta era sobre todo para saber si había algo que hacer, ya que parece que esta sobrecarga se da de forma gratuita (esta sobrecarga parece ser muy fácil de matar) No veo por qué no hacerlo.

Una palabra clave fácil para pequeñas optimizaciones, como C99 restrict, decirle al compilador que un objeto polimórfico es de tipo fijo es lo que estaba esperando.

De todos modos, solo para responder a los comentarios, un poco de sobrecarga está presente. Miro esta ad-hoc extrema código:

struct Base { virtual void f(){} }; 
struct Final : public Base { void f(){} }; 

int main() { 
    Final final; 
    Final &f = final; 
    Base &b = f; 

    for(int i = 0; i < 1024*1024*1024; ++ i) 
#ifdef BASE 
     b.f(); 
#else 
     f.f(); 
#endif 

    return 0; 
} 

Compilación y ejecución que, teniendo tiempos:

$ for OPT in {"",-O0,-O1,-O2,-O3,-Os}; do 
    for DEF in {BASE,FINAL}; do 
     g++ $OPT -D$DEF -o virt virt.cpp && 
     TIME="$DEF $OPT: %U" time ./virt; 
    done; 
    done   
BASE : 5.19                                           
FINAL : 4.21                                           
BASE -O0: 5.22                                          
FINAL -O0: 4.19                                          
BASE -O1: 3.55                                          
FINAL -O1: 1.53                                          
BASE -O2: 3.61                                          
FINAL -O2: 0.00                                          
BASE -O3: 3.58                                          
FINAL -O3: 0.00                                          
BASE -Os: 6.14                                          
FINAL -Os: 0.00 

supongo única O2, O3 y -Os se Inlining Final::f.

Y estas pruebas se han ejecutado en mi máquina, ejecutando la última GCC y una CPU AMD Athlon (tm) 64 X2 Dual Core Processor 4000+. Supongo que podría ser mucho más lento en una plataforma más barata.

+9

Por lo tanto, supongo que su código es lento, y lo perfila y encontró que el problema está en el polimorfismo. – wilhelmtell

+2

Si 'f' es virtual en' BaseType' y 'FinalType' se deriva de' BaseType', entonces 'f' también es virtual en' FinalType'. –

+1

También. 'dynamic_cast <>()' tiene un costo de verificación en tiempo de ejecución, y el costo del polimorfismo es una referencia de puntero único. Sugiero que cada vez que diga la palabra "sobrecarga", asegúrese de decir ** exactamente ** qué significa esa sobrecarga, al menos la primera vez que habla de esa sobrecarga. Solo para que quede claro lo que estamos tratando de eliminar aquí. Y entonces, ahora, supongo que perfilaste los dos enfoques y encontraste que el polimorfismo es más lento que tu truco. – wilhelmtell

Respuesta

8

Si el envío dinámico es un cuello de botella de rendimiento en su programa, entonces la forma de resolver el problema no es utilizar el despacho dinámico (no use funciones virtuales).

Puede reemplazar algunos polimorfismos en tiempo de ejecución con polimorfismo en tiempo de compilación utilizando plantillas y programación genérica en lugar de funciones virtuales. Esto puede o no dar como resultado un mejor rendimiento; solo un perfilador puede decirle con seguridad.

Para que quede claro, como ya ha señalado wilhelmtell en los comentarios a la pregunta, es raro que la sobrecarga causada por el envío dinámico sea lo suficientemente importante como para preocuparse. Esté absolutamente seguro de que es su área de alto rendimiento antes de reemplazar la comodidad incorporada con una implementación personalizada difícil de manejar.

+1

A veces estás "forzado" a usar el polimorfismo (y por lo tanto el envío dinámico, me faltaba el término hasta ahora). Por supuesto, puedes utilizar tu puntero/referencia polimórfica superior y luego usarlo (pasarlo a funciones de plantilla o lo que sea); eso es lo que tenía en mente con la solución de fundición superior. Como se dijo después de editar la publicación original, es sobre todo una duda sobre cómo funcionan las cosas. Gracias por la respuesta, por cierto. – peoro

2

Si necesita usar polimorfismo, úselo. Realmente no hay una manera más rápida de hacerlo.

Sin embargo, respondería con otra pregunta: ¿Es este su mayor problema? Si es así, tu código ya es óptimo o casi. Si no, averigua cuál es el mayor problema, y ​​concéntrate en eso.