2009-02-10 14 views

Respuesta

123

Las funciones virtuales solo tienen una sobrecarga de rendimiento muy pequeña en comparación con las llamadas directas. En un nivel bajo, básicamente está buscando en una búsqueda de matriz para obtener un puntero de función, y luego una llamada a través de un puntero de función. Las CPU modernas incluso pueden predecir llamadas a funciones indirectas razonablemente bien en sus predictores de bifurcación, por lo que generalmente no dañarán demasiado las tuberías de CPU modernas. En el nivel de ensamblaje, una llamada de función virtual se traduce a algo como lo siguiente, donde I es un valor inmediato arbitrario.

MOV EAX, [EBP + I] ; Move pointer to class instance into register 
MOV EBX, [EAX] ; Move vtbl pointer into register. 
CALL [EBX + I] ; Call function 

Vs. lo siguiente para una llamada de función directa:

CALL I ; Call function directly 

La sobrecarga real es que las funciones virtuales no se pueden insertar, en su mayor parte. (Pueden estar en lenguajes JIT si la VM se da cuenta de que siempre van a la misma dirección de todos modos.) Además de la aceleración que obtiene al alinearse, la alineación permite muchas otras optimizaciones, como el plegado constante, porque la persona que llama puede saber cómo el destinatario funciona internamente Para las funciones que son lo suficientemente grandes como para no ser inline de todos modos, el golpe de rendimiento probablemente será insignificante. Para funciones muy pequeñas que pueden estar incluidas, es cuando debe tener cuidado con las funciones virtuales.

Editar: Otra cosa a tener en cuenta es que todos los programas requieren control de flujo, y esto nunca es gratis. ¿Qué reemplazaría tu función virtual? ¿Una declaración de cambio? Una serie de declaraciones if? Estas todavía son ramas que pueden ser impredecibles. Además, dada una rama N-way, una serie de sentencias if encontrará la ruta adecuada en O (N), mientras que una función virtual la encontrará en O (1). La instrucción de cambio puede ser O (N) u O (1) dependiendo de si está optimizada para una tabla de salto.

+10

Uno de los mensajes más informativos que he visto en un tiempo que no conocía de antemano y que aún era fácilmente comprensible. –

+0

Excelente respuesta, muchas gracias. Como no estoy planeando llamar a mis métodos en un círculo muy cerrado, no me preocuparé demasiado por esta sobrecarga.Gracias por la ayuda =) –

+1

Anton Ertl mantiene una página de referencia útil (con código fuente) que incluye los costos directos y indirectos de envío de funciones para una buena variedad de procesadores: http://www.complang.tuwien.ac.at/forth/threading/ –

2

De sus etiquetas, usted está hablando C#. Solo puedo responder desde una perspectiva Delphi. Creo que será similar. (Estoy esperando comentarios negativos aquí :))

Un método estático se vinculará en tiempo de compilación. Un método virtual requiere una búsqueda en el tiempo de ejecución para decidir a qué método llamar, por lo que hay una pequeña sobrecarga. Solo es significativo si el método es pequeño y se llama con frecuencia.

0

En el lado del escritorio no importa si el método está sobrecargado o no, incurren en un nivel adicional de direccionamiento indirecto a través de la tabla de punteros del método (tabla de método virtual), lo que significa aproximadamente 2 lecturas de memoria adicionales por indirección antes del la llamada al método comparó métodos no virtuales en clases no selladas y métodos no finales.

[Como dato interesante, en la versión compact framework 1.0 del sobrecalentamiento es mayor, ya que no utiliza tablas método virtual sino simplemente la reflexión para descubrir el método derecho a ejecutar cuando se llama a un método virtual.]

también es mucho menos probable que los métodos virtuales sean candidatos para inline u otras optimizaciones, como la llamada de cola, que los métodos no virtuales.

Aproximadamente esta es la jerarquía de realización de llamadas a métodos:

no métodos virtuales < Medotodología virtuales < métodos de interfaz (en clases) < Delegado de despacho < MethodInfo.Invoke < Type.InvokeMember

Pero ninguno de estos las implicaciones de rendimiento de varios mecanismos de envío no importan a menos que lo haya probado midiendo;) (Y aun así las implicaciones de arquitectura, legibilidad, etc. podrían tener un gran peso sobre cuál elegir)

11

Normalmente, un método virtual simplemente pasa por una tabla de función-punteros para llegar al método real. Esto significa una desreferencia adicional y una vuelta más a la memoria.

Si bien el costo no es absolutamente CERO, es extremadamente mínimo. Si ayuda a tu programa a tener funciones virtuales, por supuesto, hazlo.

Es mucho mejor tener un programa bien diseñado con un rendimiento diminuto, diminuto y minúsculo en lugar de un programa torpe solo por el hecho de evitar la tabla v.

+3

El mayor costo de las llamadas a funciones virtuales no es la carga del puntero desde el vtable, sino la eliminación del pipeline que resulta de una rama mal predicha (y los vjumps son generalmente mal interpretado). Eso puede ser tan largo como la tubería misma. Para las funciones llamadas con frecuencia, se suma. – Crashworks

+0

@Crashworks: pensamientos interesantes. Sin embargo, parece contradecir algunos de los comentarios en esta otra pregunta (http://stackoverflow.com/questions/10757167/do-function-pointers-force-an-instruction-pipeline-to-clear). ¿Te importa comentar? (No tengo idea de quién tiene la razón ... solo trato de absorber toda la información que pueda) – abelenky

+1

La mayoría de las respuestas en esa pregunta son incorrectas, especialmente para los procesadores en orden, porque una rama indirecta generalmente es mal interpretada. – Crashworks

4

Es difícil de decir con certeza, porque el compilador .NET JIT puede optimizar la sobrecarga en algunos (¿muchos?) Casos.

Pero si no lo optimiza, básicamente estamos hablando de un direccionamiento adicional del puntero.

Es decir, cuando se llama a un método no virtual, usted tiene que

  1. Guardar registros, generar la función de prólogo/epílogo de elaborar argumentos, copiar el valor de retorno y tal.
  2. salto a un fijo, y estáticamente conocido, dirección

1 es el mismo en ambos casos. En cuanto a 2, con un método virtual, en lugar de eso, debe leer desde un desplazamiento fijo en la vtable del objeto, y luego ir a donde sea que apunte. Eso hace que la predicción de bifurcación sea más difícil, y puede sacar algunos datos de la memoria caché de la CPU. Entonces, la diferencia no es enorme, pero puede sumarse si haces que todas las llamadas de función sean virtuales.

También puede inhibir las optimizaciones. El compilador puede alinear fácilmente una llamada a una función no virtual, porque sabe exactamente a qué función se llama. Con una función virtual, eso es un poco más complicado. El compilador JIT aún puede hacerlo, una vez que se determina qué función se llama, pero es mucho más trabajo.

Con todo, todavía puede sumar, especialmente en áreas de rendimiento crítico. Pero no es algo de lo que deba preocuparse a menos que la función se llame al menos unos cientos de miles de veces por segundo.

4

I ran this test in C++. Una llamada de función virtual toma (en un PowerPC de 3 ghz) entre 7 y 20 nanosegundos más que una llamada de función directa. Eso significa que realmente solo importa para las funciones que planea llamar un millón de veces por segundo, o para funciones que son tan pequeñas que la sobrecarga puede ser mayor que la función en sí misma. (Por ejemplo, hacer que las funciones de acceso virtual salgan de un hábito a ciegas es probablemente imprudente.)

No he realizado mi prueba en C#, pero espero que la diferencia sea aún menor, ya que casi todas las operaciones en el CLR implican un indirecto de todos modos.

+0

"tiempo de ejecución interpretado"? ah cmon people, .Net ni siquiera es una máquina virtual real, y todavía hay personas después de 9 años que piensan .Net se interpreta ... pff –

+0

mi mal - corregido. – Crashworks

14

Rico Mariani se describen problemas relativos al comportamiento en su Performance Tidbits blog, donde afirmó:

métodos virtuales: ¿Está utilizando métodos virtuales cuando las llamadas directas haría? Muchas veces las personas van con métodos virtuales para permitir la futura extensibilidad de . La extensibilidad es un bien de pero tiene un precio de - asegúrese de que su extensibilidad completa historia está resuelta y que su uso de funciones virtuales va realmente al para llegar a donde necesita estar. Por ejemplo, a veces las personas piensan a través de los problemas del sitio de llamadas pero luego no consideran cómo van a crearse los objetos "extendidos" . Más tarde se dan cuenta de que (la mayoría de) las funciones virtuales no ayudaron en absoluto y necesitaban un modelo totalmente diferente para obtener los objetos "extendidos" en el sistema.

Sellado: sellado puede ser una forma de limitar el polimorfismo de la clase sólo aquellos sitios donde se necesita polimorfismo. Si va a controlar completamente el tipo, entonces el sellado puede ser una gran cosa para el rendimiento ya que permite llamadas directas y en línea.

Básicamente, el argumento en contra de los métodos virtuales es que no permite que el código sea un candidato de revestimiento, en oposición a las llamadas directas.

En el artículo de MSDN Improving .NET Application Performance and Scalability, esto se expone más adelante:

Considerar las soluciones de compromiso de los miembros virtuales

Use miembros virtuales que proporcionan extensibilidad. Si no necesita extender el diseño de su clase , evite los miembros virtuales porque son más caros de llamar debido a una búsqueda de tabla virtual y anulan ciertas optimizaciones de rendimiento en tiempo de ejecución. Por ejemplo, los miembros virtuales no pueden ser insertados por el compilador. Además, cuando permite la subtipificación, en realidad presenta un contrato muy complejo a los consumidores e inevitablemente terminará con problemas de versiones cuando intente actualizar su clase en el futuro.

Una crítica de lo anterior, sin embargo, proviene del campo de TDD/BDD (el cual quiere que los métodos por defecto a virtual) argumentando que el impacto en el rendimiento es insignificante de todos modos, especialmente a medida que el acceso a máquinas mucho más rápido.

Cuestiones relacionadas