2009-05-06 8 views
5

En los programas del día a día ni siquiera me molestaría en pensar en el posible golpe de rendimiento para codificar contra interfaces en lugar de implementaciones. Las ventajas superan en gran medida el costo. Entonces, por favor, ningún consejo genérico sobre un buen OOP.¿La codificación hacia una interfaz en lugar de una implementación implica un golpe de rendimiento?

Sin embargo, en la publicación this, el diseñador de la plataforma XNA (juego) ofrece como argumento principal que no ha diseñado las clases principales de su infraestructura contra una interfaz que implicaría un golpe de rendimiento. Al ver que es en el contexto de un desarrollo de juegos donde posiblemente todos los fps cuenten, creo que es una pregunta válida que debe hacerse.

¿Alguien tiene alguna estadística al respecto? No veo una buena manera de probar/medir esto ya que no sé qué implicaciones debo tener en cuenta con un objeto de este tipo (gráficos).

+0

Su pregunta todavía es válida pero está diciendo mal el nombre del hombre. Él está diciendo que proporcionar las interfaces * simplemente para proporcionar simulaciones para las pruebas unitarias * no valía la pena el rendimiento alcanzado. Lo cual puedo ver su punto. –

+0

No lo sé. ¿Cuánto de un golpe de perforación serviría?¿Cuánto vale para ti poder probar algo? –

Respuesta

2

Interfaces generalmente implica algunos golpes con el rendimiento (esto sin embargo puede cambiar dependiendo del idioma/tiempo de ejecución utilizado):

  1. métodos de interfaz se implementan normalmente a través de una llamada virtual por el compilador. Como señala otro usuario, el compilador no puede insertarlos, por lo que pierde esa ganancia potencial. Además, agregan algunas instrucciones (saltos y acceso a memoria) como mínimo para obtener la PC adecuada en el segmento de código.
  2. Las interfaces, en varios idiomas, también implican un gráfico y requieren un DAG (gráfico acíclico dirigido) para administrar correctamente la memoria. En varios idiomas/tiempos de ejecución, puede obtener una "fuga" de memoria en el entorno administrado al tener un gráfico cíclico. Esto impone una gran tensión (obviamente) en el recolector de basura/memoria en el sistema. ¡Cuidado con los gráficos cíclicos!
  3. Algunos lenguajes utilizan una interfaz de estilo COM como su interfaz subyacente, llamando automáticamente a AddRef/Release cada vez que la interfaz se asigna a un local o se pasa por valor a una función (utilizada para la gestión del ciclo de vida). Estas llamadas AddRef/Release pueden sumarse y ser bastante costosas. Algunos lenguajes han tenido esto en cuenta y pueden permitirle pasar una interfaz como 'const' que no generará el par AddRef/Release reduciendo automáticamente estas llamadas.

Aquí es un pequeño ejemplo de un gráfico cíclico donde Referencia 2 interfaces entre sí y tampoco serán automáticamente recogidos como sus refcounts siempre serán mayores que 1.

interface Parent { 
    Child c; 
} 

interface Child { 
    Parent p; 
} 

function createGraph() { 
    ... 
    Parent p = ParentFactory::CreateParent(); 
    Child c = ChildFactory::CreateChild(); 

    p.c = c; 
    c.p = p;  
    ... // do stuff here 

    // p has a reference to c and c has a reference to p. 
    // When the function goes out of scope and attempts to clean up the locals 
    // it will note that p has a refcount of 1 and c has a refcount of 1 so neither 
    // can be cleaned up (of course, this is depending on the language/runtime and 
    // if DAGS are allowed for interfaces). If you were to set c.p = null or 
    // p.c = null then the 2 interfaces will be released when the scope is cleaned up. 
} 
+0

aunque no voy a pretender entenderlo todo, veo una respuesta educada. Hora para algunos reasearch –

+0

¿Puedes dar un ejemplo de cómo crear un gráfico cíclico? Pseudo código tal vez? –

+0

¿El GC no verifica si la referencia a Padre/Hijo corresponde a un objeto que también está marcado para Verificación de basura? Pero pude ver cómo un control de este tipo tendría un límite estricto en la profundidad para el rendimiento. No tengo datos exactos sobre eso, pero creo que el .net GC tendría salvaciones para tal cosa. –

0

En mi opinión personal, todo el trabajo realmente pesado cuando se trata de gráficos se transmite a la GPU anwyay. Esto libera su CPU para hacer otras cosas como flujo de programa y lógica. No estoy seguro de si hay un golpe de rendimiento cuando se programa en una interfaz pero pensando en la naturaleza de los juegos, no es algo que deba ser extensible. Tal vez ciertas clases pero, en general, no creo que un juego deba programarse teniendo en cuenta la extensibilidad. Así que adelante, codifica la implementación.

+0

No es tan extensible (aunque podría justificarlo) pero la capacidad de prueba es mi principal problema. Por favor, consulte http://stackoverflow.com/questions/804904/xna-mock-the-game-object/828482#828482 –

6

La codificación en una interfaz siempre será más fácil, simplemente porque las interfaces, si se hacen bien, son mucho más simples. Es palpablemente más fácil escribir un programa correcto usando una interfaz.

Y como dice la antigua máxima, es más fácil hacer que un programa correcto funcione más rápido que hacer que un programa rápido se ejecute correctamente.

Así que programe en la interfaz, haga que todo funcione y luego haga algunos perfiles para ayudarlo a cumplir con los requisitos de rendimiento que pueda tener.

+0

Pero no pude diseñar el framework XNA, por lo que no hay interfaz disponible .. –

+1

Entonces en ese caso específico no tiene otra opción, entonces cualquier comparación es discutible. – PaulJWilliams

+0

+1 para la máxima y el consejo sólido – annakata

1

Creo que la duración del objeto y la cantidad de instancias que está creando proporcionarán una respuesta de grano grueso.

Si está hablando de algo que tendrá miles de instancias, con vidas cortas, supongo que probablemente sea mejor hacerlo con una estructura en lugar de una clase, y mucho menos una clase que implemente una interfaz.

Para algo más parecido a un componente, con un número bajo de instancias y una vida útil de moderada a larga, no me puedo imaginar que vaya a hacer mucha diferencia.

3

Primero, diría que la idea común es que el tiempo de los programadores suele ser más importante, y trabajar en contra de la implementación probablemente forzará mucho más trabajo cuando cambie la implementación.

En segundo lugar con el compilador adecuado/Jit, supongo que trabajar con la interfaz requiere una cantidad de tiempo ridículamente pequeña en comparación con el trabajo en contra de la implementación en sí. Además, las técnicas como plantillas pueden eliminar la ejecución del código de interfaz.

Tercero, para citar a Knuth: "Deberíamos olvidarnos de pequeñas eficiencias, digamos aproximadamente el 97% del tiempo: la optimización prematura es la raíz de todo mal".
Así que sugeriría que primero se codifique bien, y solo si está seguro de que hay un problema con la interfaz, solo entonces consideraría cambiar.

También supongo que si este golpe de rendimiento fuera cierto, la mayoría de los juegos no habrían utilizado un enfoque OOP con C++, pero este no es el caso, este Article explica un poco al respecto.

Es difícil hablar de las pruebas de forma general, naturalmente, un programa malo puede pasar mucho tiempo en malas interfaces, pero dudo que esto sea cierto para todos los programas, por lo que debería ver cada programa en particular.

+3

Por ejemplo, el JIT de Java sabrá cuándo una interfaz tiene solo una implementación cargada, por lo que puede optimizar las llamadas a métodos para que no sean virtuales. Luego, si se carga otra implementación en tiempo de ejecución, el JIT desoptimizará el código previamente optimizado y lo recompilará. –

3

What Things Cost in Managed Code

"No parece haber una diferencia significativa en el costo de una llamada prima estática, llamada ejemplo, llamada virtual, o la llamada interfaz."

Depende de la cantidad de código que se inserte o no en el tiempo de compilación, lo que puede aumentar el rendimiento ~ 5x.

También lleva más tiempo codificar a las interfaces, porque debe codificar el contrato (interfaz) y luego la implementación concreta.

Pero hacer las cosas bien siempre lleva más tiempo.

+1

En realidad, creo que hacer las cosas bien lleva menos tiempo a la larga :). –

+0

Bueno para ti, pero mi comentario fue en el contexto de la construcción de la primera vez, no de mantenimiento –

+0

Tienes toda la razón, estoy de acuerdo. –

0

que implicaría un impacto en el rendimiento

El diseñador debe ser capaz de demostrar su opinión.

1

OMI sí, pero para una el diseño fundamental es mucho más sutil y complejo que el despacho virtual o las consultas de interfaz tipo COM o los metadatos de objeto necesarios para la información del tipo de tiempo de ejecución o algo así. Hay una sobrecarga asociada con todo eso, pero depende en gran medida del lenguaje y compilador (s) utilizado, y también depende de si el optimizador puede eliminar dicha sobrecarga en tiempo de compilación o tiempo de enlace.Sin embargo, en mi opinión hay una razón más amplia conceptual razón por la codificación de una interfaz implica (no garantías) un impacto en el rendimiento:

Codificación a una interfaz implica que hay una barrera entre usted y los datos concreto/memoria que desea para acceder y transformar

Esta es la razón principal por la que veo. Como ejemplo muy simple, digamos que tiene una interfaz de imagen abstracta. Se abstrae completamente sus detalles concretos, como su formato de píxeles. El problema aquí es que a menudo las operaciones de imagen más eficientes necesitan esos detalles concretos. No podemos implementar nuestro filtro de imagen personalizado con instrucciones SIMD eficientes, por ejemplo, si tuviéramos que llamar a getPixel de a uno por vez y setPixel uno a la vez y sin tener en cuenta el formato de píxel subyacente.

Por supuesto, la imagen abstracta podría tratar de proporcionar todas estas operaciones, y esas operaciones podrían implementarse muy eficientemente ya que tienen acceso a los detalles internos privados de la imagen concreta que implementa esa interfaz, pero eso solo se sostiene como siempre que la interfaz de la imagen proporcione todo lo que el cliente alguna vez quiera hacer con una imagen.

A menudo, en algún punto una interfaz no puede proporcionar todas las funciones imaginables al mundo entero, por lo que tales interfaces, cuando se enfrentan a preocupaciones de rendimiento crítico y al mismo tiempo necesitan satisfacer una amplia gama de necesidades, a menudo detalles. La imagen abstracta aún puede proporcionar, por ejemplo, un puntero a sus píxeles subyacentes a través de un método pixels() que en gran medida frustra mucho del propósito de la codificación en una interfaz, pero a menudo se convierte en una necesidad en las áreas más críticas para el rendimiento.

En general, muchos de los códigos más eficientes a menudo tienen que escribirse con detalles muy concretos en algún nivel, como el código escrito específicamente para coma flotante de precisión simple, código escrito específicamente para imágenes RGBA de 32 bits, código escrito específicamente para GPU, específicamente para AVX-512, específicamente para hardware móvil, etc. Así que hay una barrera fundamental, al menos con las herramientas que tenemos hasta ahora, donde no podemos abstraer todo eso y simplemente codificar una interfaz sin una implícita pena.

Por supuesto, nuestras vidas serían mucho más sencillas si pudiéramos simplemente escribir código, ajenos a todos los detalles concretos, como si estamos trabajando con SPFP de 32 bits o DPFP de 64 bits, si estamos escribiendo sombreadores en un dispositivo móvil limitado o una computadora de escritorio de alta gama, y ​​todo es el código más competitivo y competitivo que existe. Pero estamos lejos de esa etapa. Nuestras herramientas actuales a menudo requieren que escribamos nuestro código de rendimiento crítico contra detalles concretos.

Y, por último, este es un problema de granularidad. Naturalmente, si tenemos que trabajar con cosas píxel por píxel, cualquier intento de abstraer detalles concretos de un píxel podría ocasionar una importante penalización del rendimiento. Pero si estamos expresando cosas en el nivel de la imagen como "mezclar alfa estas dos imágenes juntas", podría ser un costo muy despreciable, incluso si hay una sobrecarga de despacho virtual y demás. Entonces, a medida que trabajamos hacia un código de nivel superior, a menudo cualquier penalización implícita en el rendimiento de la codificación a una interfaz disminuye hasta el punto de volverse completamente trivial. Pero siempre existe la necesidad del código de bajo nivel, que sí hace cosas como procesar cosas píxel por píxel, recorriendo millones de ellas muchas veces por fotograma, y ​​allí el costo de codificación en una interfaz puede llevar una bonita penalidad sustancial, aunque solo sea porque está ocultando los detalles concretos necesarios para escribir la implementación más eficiente.

Cuestiones relacionadas