Pregunta ¿La codificación hacia una interfaz en lugar de una implementación implica un golpe de rendimiento?


En los programas diarios, ni siquiera me molestaría en pensar sobre el posible golpe de rendimiento para codificar contra interfaces en lugar de implementaciones. Las ventajas superan ampliamente el costo. Así que por favor no hay consejos genéricos en buen OOP.

Sin embargo en esta Posteriormente, el diseñador de la plataforma XNA (juego) da como argumento principal no haber diseñado las clases centrales de su marco de trabajo contra una interfaz que implicaría un impacto en el 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 sobre eso? 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).


5
2018-05-06 07:47


origen


Respuestas:


Las interfaces generalmente implican algunos impactos en el rendimiento (sin embargo, esto puede cambiar dependiendo del idioma / tiempo de ejecución utilizado):

  1. Los métodos de interfaz generalmente se implementan a través de una llamada virtual por parte del compilador. Como señala otro usuario, el compilador no puede incluirlos en línea, por lo que se 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 adecuadamente la memoria. En varios idiomas / tiempos de ejecución, puede obtener una 'pérdida' 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 idiomas usan una interfaz de estilo COM como su interfaz subyacente, y llaman 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 administración del ciclo de vida). Estas llamadas AddRef / Release pueden sumarse y ser bastante costosas. Algunos idiomas han explicado esto y pueden permitirle pasar una interfaz como 'const' que no generará el par AddRef / Release que reduce automáticamente estas llamadas.

Este es un pequeño ejemplo de un gráfico cíclico en el que 2 interfaces se hacen referencia entre sí y ninguna se recopilará automáticamente, ya que sus recuentos 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.
}

2
2018-05-06 20:38



Codificar a una interfaz siempre será más fácil, simplemente porque las interfaces, si se hacen correctamente, son mucho más simples. Es palpablemente más fácil escribir un programa correcto usando una interfaz.

Y a medida que avanza la máxima, es más fácil hacer que un programa correcto se ejecute más rápido que hacer que un programa rápido se ejecute correctamente.

Así que programa a la interfaz, haz que todo funcione y entonces Haga algunos perfiles para ayudarlo a cumplir con los requisitos de rendimiento que pueda tener.


6
2018-05-06 07:55



Qué cosas cuestan en el código administrado

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

Depende de la cantidad de su código que esté en línea o no en tiempo de compilación, lo que puede aumentar el rendimiento ~ 5x.

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

Pero hacer las cosas de la manera correcta siempre lleva más tiempo.


3
2018-05-06 08:17



Primero, diría que la concepción 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 la implementación cambie.

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

Tercero, para citar a Knuth: "Debemos olvidarnos de las pequeñas eficiencias, digamos que aproximadamente el 97% de las veces: la optimización prematura es la raíz de todo mal".
Por lo tanto, sugeriría una buena codificación primero, y solo si está seguro de que hay un problema con la Interfaz, solo entonces consideraría cambiar.

También supondría que si este éxito de rendimiento fuera cierto, la mayoría de los juegos no hubieran usado un enfoque OOP con C ++, pero este no es el caso, este Artículo elabora un poco al respecto.

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


3
2018-05-06 08:06



Creo que la vida útil de los objetos y la cantidad de instancias que está creando proporcionarán una respuesta de grano grueso.

Si estás hablando de algo que tendrá miles de instancias, con tiempos de vida cortos, 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 puedo imaginar que vaya a hacer mucha diferencia.


1
2018-05-06 07:59



IMO sí, pero por un motivo de diseño fundamental mucho más sutil y complejo que el envío virtual o consultas de interfaz tipo COM o metadatos de objeto necesarios para la información de tipo de tiempo de ejecución o algo por el estilo. 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 conceptual más amplia por la que la codificación a una interfaz implica (no garantiza) un impacto de rendimiento:

La codificación de una interfaz implica que existe una barrera entre usted y   los datos / memoria concretos que desea acceder y transformar.

Esta es la razón principal que veo. Como ejemplo muy simple, supongamos que tiene una interfaz de imagen abstracta. Extrae 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 getPixeluno a la vez y setPixel uno a la vez y mientras se olvida del formato de píxel subyacente.

Por supuesto, la imagen abstracta podría tratar de proporcionar todas estas operaciones, y esas operaciones podrían implementarse de manera muy eficiente ya que tienen acceso a los detalles internos privados de la imagen concreta que implementa esa interfaz, pero eso solo se mantiene mientras la imagen La interfaz proporciona todo lo que el cliente querría hacer con una imagen.

A menudo, en algún momento una interfaz no puede ofrecer todas las funciones imaginables para el mundo entero, por lo que dichas interfaces, cuando se enfrentan a problemas de rendimiento crítico y al mismo tiempo necesitan satisfacer una amplia gama de necesidades, a menudo filtran sus detalles concretos. La imagen abstracta aún podría proporcionar, digamos, un puntero a sus píxeles subyacentes a través de un pixels() método que anula en gran medida el propósito de codificar 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 tienen que escribirse a menudo contra 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 y codificar a una interfaz sin una penalización implícita.

Por supuesto, nuestras vidas se volverían mucho más fáciles si pudiéramos simplemente escribir código, ajenos a todos los detalles concretos como si estamos tratando con SPFP de 32 bits o DPFP de 64 bits, ya sea que estemos escribiendo sombreadores en un dispositivo móvil limitado o un escritorio de alta gama, y ​​todo es el código más eficiente desde el punto de vista competitivo. Pero estamos lejos de esa etapa. Nuestras herramientas actuales a menudo aún requieren que escribamos nuestro código de rendimiento crítico en contra de detalles concretos.

Y, por último, esto es una especie 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. Así que a medida que trabajamos hacia un código de nivel superior, a menudo cualquier penalización de rendimiento implícita 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.


1
2018-01-16 10:07



En mi opinión personal, todo el trabajo realmente pesado cuando se trata de gráficos se transmite a la GPU. Esto libera su CPU para hacer otras cosas como la lógica y el flujo de programas. No estoy seguro de si hay un impacto en el rendimiento cuando se programa en una interfaz, pero si se piensa en la naturaleza de los juegos, no es algo que deba extenderse. 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
2018-05-06 07:52



implicaría un éxito de rendimiento

El diseñador debería ser capaz de demostrar su opinión.


0
2018-05-06 18:55