2011-07-06 9 views
5

Estoy en una situación en la que tengo objetos del juego que tienen una función virtual Update(). Hay muchos objetos de juego (actualmente un poco más de 7000) y las llamadas de bucle se actualizan para todos ellos (entre otras cosas). Mi colega sugirió que deberíamos eliminar la función virtual por completo. Como se puede imaginar, esto requerirá mucha refactorización.Costo de una función virtual en un círculo cerrado

He visto this answer pero en mi caso, la creación de perfiles significa que tengo que cambiar una gran cantidad de código. Entonces, antes de siquiera pensar en comenzar, pensé en pedir aquí una opinión sobre si la refacturación vale la pena en este caso.

Tenga en cuenta que he perfilado otras partes del circuito y he estado tratando de optimizar las partes que llevan más tiempo. Sospecho que las llamadas a funciones virtuales en este caso es algo de lo que no debería preocuparme, pero no puedo estar seguro hasta que perfilé y no puedo crear un perfil hasta que cambie el código (que es mucho). También tenga en cuenta que algunas funciones de actualización son muy pequeñas, mientras que otras son más grandes y complejas.

EDIT: Hay varias respuestas que le dan una gran idea, por lo que cualquiera que tropiece con esta pregunta en el futuro, eche un vistazo a todas las respuestas y no solo a la seleccionada.

+2

Según la información que proporcionó, parece que la refabricación podría ser difícil o imposible. La razón es que hay varios tipos diferentes de funciones Update(). Todo lo que obtendrá con la refactorización es que las llamadas a funciones virtuales serán reemplazadas por sentencias switch-case o if, que no son mejores en rendimiento. – tp1

+2

¡Cómo se atreve alguien a preguntar sobre la optimización! : P ... como de costumbre, -1 en este tipo de preguntas sin explicar por qué. De todos modos, gracias a todos los que respondieron. Todo lo que necesitaba para obtener otra opinión, que obtuve. No estoy seguro de por qué algunas personas están empeñadas en enterrar todo y cualquier cosa que incluso tenga la palabra 'optimizar' – Samaursa

+0

Lo mismo aquí. Estoy trabajando en mi propio motor de juego, que actualmente se ejecuta a unos 10 fps en una máquina de primera línea. No estoy seguro de dónde mirar, pero tengo miedo de preguntar. En mi motor estoy usando dos listas, una para todos los objetos (que superan en número a los objetos en vivo en mi sistema - las colisiones no hacen que un objeto esté activo) y uno para los objetos en vivo. – dascandy

Respuesta

6

Si no puede crear un perfil, eche un vistazo al código del ensamblador para hacerse una idea de lo caro que es realmente la búsqueda. Puede ser un simple salto indirecto que no cuesta casi nada.

Si necesita refactorizar, aquí hay una sugerencia: Cree muchas clases de "UpdateXxx" que sepan cómo llamar al nuevo método no virtual update(). Recoge esos en una matriz y luego llama al update().

Pero supongo que no ahorrará mucho, especialmente no solo con objetos de 7K.

Nota sobre la creación de perfiles: si no puede usar un generador de perfiles (me pregunto por qué no), sincronice las llamadas al update() y registre las llamadas que tarden más de, por ejemplo, 100 ms. El tiempo no es costoso y le permite descubrir rápidamente qué llamadas son más caras.

+0

Esa es una muy buena sugerencia. Teniendo en cuenta su respuesta y la de dascandy, me olvidaré de esto por ahora, pero en mi tiempo libre le daré una oportunidad a su sugerencia y veremos qué sucede (+1) – Samaursa

10

Una llamada de función virtual no va a agregar mucho más que un solo indirecto y un salto difícil de predecir. Eso significa que, por lo general, está abajo de un oleoducto o aproximadamente 20 ciclos por función virtual. 7000 de ellos son aproximadamente 140000 ciclos, lo que debería ser insignificante en comparación con su función de actualización promedio. Si no es así, digamos que la mayoría de las funciones de actualización están vacías, puede considerar colocar los objetos actualizables en una lista separada para este propósito.

La eliminación de la función virtual solo conducirá a que uno de ustedes la reemplace con un sistema idéntico pero autoaplicado. Este es el tipo exacto de lugar donde una función virtual tiene sentido.

Por referencia, 140000 ciclos es de aproximadamente 50 microsegundos. Eso supone un P4 con una tubería enorme y siempre una descarga de tubería completa (que normalmente no se obtiene).

+0

Excelente, eso definitivamente ayudará en mi decisión, gracias (+1) – Samaursa

8

Aunque no es el mismo código y no puede ser el mismo compilador como eres utilizando, aquí hay un poco de datos de referencia a partir de un punto de referencia en lugar de edad (banco ++ por Joe Orost):

Test Name: F000005       Class Name: Style 
CPU Time:  7.70 nanoseconds   plus or minus  0.385 
Wall/CPU:  1.00 ratio.    Iteration Count: 1677721600 
Test Description: 
Time to test a global using a 10-way if/else if statement 
compare this test with F000006 


Test Name: F000006       Class Name: Style 
CPU Time:  2.00 nanoseconds   plus or minus  0.0999 
Wall/CPU:  1.00 ratio.    Iteration Count: 1677721600 
Test Description: 
Time to test a global using a 10-way switch statement 
compare this test with F000005 


Test Name: F000007       Class Name: Style 
CPU Time:  3.41 nanoseconds   plus or minus  0.171 
Wall/CPU:  1.00 ratio.    Iteration Count: 1677721600 
Test Description: 
Time to test a global using a 10-way sparse switch statement 
compare this test with F000005 and F000006 


Test Name: F000008       Class Name: Style 
CPU Time:  2.20 nanoseconds   plus or minus  0.110 
Wall/CPU:  1.00 ratio.    Iteration Count: 1677721600 
Test Description: 
Time to test a global using a 10-way virtual function class 
compare this test with F000006 

Este resultado particular es de compilar con la edición de 64 bits de VC++ 9.0 (VS 2008), pero es razonablemente similar a lo que he visto en otros compiladores recientes. La conclusión es que la función virtual es más rápida que la mayoría de las alternativas obvias, y muy casi a la misma velocidad que la única que la supera (de hecho, si las dos son iguales está dentro del margen de error medido). Sin embargo, eso depende de que los valores involucrados sean densos: como se puede ver en F00007, si los valores son escasos, la sentencia switch produce un código que es más lento que la llamada a la función virtual.

En pocas palabras: la llamada a la función virtual es probablemente el lugar equivocado para buscar. El código refactorizado puede funcionar más lento, e incluso en el mejor de los casos probablemente no obtendrá lo suficiente como para notarlo o preocuparse por él.

+0

Guau, excelente, gracias por tomarse el tiempo para hacerlo. Refuerza mi decisión de no preocuparme por ello (+1) – Samaursa

+0

+1 Como una idea de último momento: C++ ha existido por un par de años y no es sorprendente que los desarrolladores de hardware hayan comenzado a construir CPU que puedan ejecutarlo de manera eficiente. :-) –

+0

@Jerry: ¿Podría proporcionar un enlace a la fuente por casualidad? Parece que no puedo obtener resultados de búsqueda para el banco ++ – Samaursa

Cuestiones relacionadas