¿La orientación a objetos realmente afecta el rendimiento del algoritmo?

14

La orientación a objetos me ha ayudado mucho en la implementación de muchos algoritmos. Sin embargo, los lenguajes orientados a objetos a veces lo guían en un enfoque "directo" y dudo que este enfoque sea siempre algo bueno.

OO es realmente útil para codificar algoritmos de manera rápida y fácil. Pero, ¿podría esta OOP ser una desventaja para el software basado en el rendimiento, es decir, qué tan rápido se ejecuta el programa?

Por ejemplo, almacenar nodos de gráficos en una estructura de datos parece "sencillo" en primer lugar, pero si los objetos Node contienen muchos atributos y métodos, ¿podría esto conducir a un algoritmo lento?

En otras palabras, ¿podrían muchas referencias entre muchos objetos diferentes, o el uso de muchos métodos de muchas clases, dar como resultado una implementación "pesada"?

Florent Tselai
fuente
1
Una pregunta bastante extraña. Puedo entender cómo OOP ayuda en un nivel de arquitectura. Pero un nivel de implementación de algoritmos normalmente se basa en abstracciones que son muy ajenas a todo lo que OOP representa. Entonces, lo más probable es que el rendimiento no sea el mayor problema para sus implementaciones de algoritmos OOP. En cuanto al rendimiento, con OOP, el mayor cuello de botella está normalmente relacionado con las llamadas virtuales.
SK-logic
@ SK-logic> la orientación a objetos tiende a manipular todo por puntero, lo que implica una carga de trabajo más importante en el lado de la asignación de memoria, y los datos no localizados tienden a no estar en la memoria caché de la CPU y, por último, pero no menos importante, implican una gran cantidad de trabajo indirecto ramificación (funciones virtuales) que es mortal para la canalización de la CPU. OO es algo bueno, pero ciertamente puede tener un costo de rendimiento en algunos casos.
deadalnix
Si los nodos en su gráfico tienen cien atributos, necesitará un lugar para almacenarlos, independientemente del paradigma utilizado para la implementación real, y no veo cómo un paradigma único tiene una ventaja en esto en general. @deadalnix: Tal vez los factores constantes pueden ser peores debido a que ciertas optimizaciones son más difíciles. Pero tenga en cuenta que digo más difícil , no imposible : por ejemplo, PyPy puede descomprimir objetos en bucles estrechos y las JVM han estado incorporando llamadas a funciones virtuales desde siempre.
Python es bueno para los algoritmos de creación de prototipos y, sin embargo, con frecuencia no necesita una clase cuando implementa un algoritmo típico en él.
Trabajo
1
1 Para la orientación a objetos en relación con los algoritmos, algo que se pasa por alto en estos días, tanto en la industria del software, y la academia ...
umlcat

Respuestas:

16

La orientación a objetos puede evitar ciertas optimizaciones algorítmicas, debido a la encapsulación. Dos algoritmos pueden funcionar particularmente bien juntos, pero si están ocultos detrás de las interfaces OO, se pierde la posibilidad de usar su sinergia.

Mira las bibliotecas numéricas. Muchos de ellos (no solo los escritos en los años 60 o 70) no son OOP. Hay una razón para eso: los algoritmos numéricos funcionan mejor como un conjunto de desacoplamientos modulesque como jerarquías OO con interfaces y encapsulación.

cuant_dev
fuente
2
La razón principal de esto es que solo C ++ descubrió el uso de plantillas de expresión para hacer que la versión OO sea tan eficiente.
DeadMG
44
Mire las bibliotecas modernas de C ++ (STL, Boost): tampoco son OOP. Y no solo por el rendimiento. Los algoritmos normalmente no se pueden representar bien en un estilo OOP. Cosas como la programación genérica son mucho más adecuadas para algoritmos de bajo nivel.
SK-logic
3
Que-que-que? Supongo que vengo de un planeta diferente al quant_dev y SK-logic. No, un universo diferente. Con diferentes leyes de la física y todo.
Mike Nakis
55
@MikeNakis: la diferencia en el punto de vista radica en (1) si una determinada pieza de código computacional puede beneficiarse en términos de legibilidad humana de OOP (que no son recetas numéricas); (2) si el diseño de la clase OOP se alinea con la estructura de datos y el algoritmo óptimos (ver mi respuesta); y (3) si cada capa de indirección entrega suficiente "valor" (en términos de trabajo realizado por llamada de función o claridad conceptual por capa) justifica la sobrecarga (debido a la indirección, llamada de función, capas o copia de datos). (4) Finalmente, la sofisticación del compilador / JIT / optimizador es un factor limitante.
rwong
2
@ MikeNakis, ¿qué quieres decir? ¿Crees que STL es una biblioteca OOP? La programación genérica no funciona bien con OOP de todos modos. Y no hace falta mencionar que OOP es un marco demasiado estrecho, adecuado solo para muy pocas tareas prácticas, ajeno a cualquier otra cosa.
SK-logic
9

¿Qué determina el rendimiento?

Los fundamentos: estructuras de datos, algoritmos, arquitectura de computadoras, hardware. Más gastos generales.

Un programa OOP puede diseñarse para alinearse exactamente con la elección de estructuras de datos y algoritmos que la teoría CS considera óptimos. Tendrá la misma característica de rendimiento que el programa óptimo, más algunos gastos generales. La sobrecarga generalmente se puede minimizar.

Sin embargo, un programa que inicialmente está diseñado solo con preocupaciones de OOP, sin importar los fundamentos, puede ser inicialmente subóptimo. La suboptimidad a veces se puede eliminar refactorizando; a veces no lo es, lo que requiere una reescritura completa.

Advertencia: ¿importa el rendimiento en el software empresarial?

Sí, pero el tiempo de comercialización (TTM) es más importante, por órdenes de magnitud. El software empresarial pone el énfasis en la adaptabilidad del código a las complejas reglas comerciales. Las mediciones de rendimiento deben tomarse durante todo el ciclo de vida del desarrollo. (Consulte la sección: ¿qué significa el rendimiento óptimo? ). Solo se deben realizar mejoras comercializables, y se deben introducir gradualmente en versiones posteriores.

¿Qué significa un rendimiento óptimo?

En general, el problema con el rendimiento del software es que: para demostrar que "existe una versión más rápida", primero debe existir esa versión más rápida (es decir, no hay otra prueba que no sea ella misma).

A veces, esa versión más rápida se ve por primera vez en un lenguaje o paradigma diferente. Esto debe tomarse como una pista para mejorar, no como un juicio de inferioridad de otros lenguajes o paradigmas.

¿Por qué estamos haciendo OOP si puede dificultar nuestra búsqueda de un rendimiento óptimo?

OOP introduce gastos generales (en espacio y ejecución), a cambio de mejorar la "trabajabilidad" y, por lo tanto, el valor comercial del código. Esto reduce el costo de un mayor desarrollo y optimización. Ver @MikeNakis .

¿Qué partes de OOP pueden fomentar un diseño inicialmente subóptimo?

Las partes de OOP que (i) fomentan la simplicidad / intuición, (ii) el uso de métodos de diseño coloquial en lugar de los fundamentos, (iii) desalienta implementaciones personalizadas múltiples con el mismo propósito.

  • BESO
  • YAGNI
  • SECO
  • Diseño de objetos (p. Ej., Con tarjetas CRC) sin dar los mismos pensamientos a los fundamentales

La aplicación estricta de algunas pautas de OOP (encapsulación, transmisión de mensajes, hacer una cosa bien) dará como resultado un código más lento al principio. Las mediciones de rendimiento ayudarán a diagnosticar esos problemas. Siempre que la estructura de datos y el algoritmo se alineen con el diseño óptimo predicho por la teoría, la sobrecarga generalmente se puede minimizar.

¿Cuáles son las mitigaciones comunes para los gastos generales de OOP?

Como se indicó anteriormente, el uso de estructuras de datos que son óptimas para el diseño.

Algunos idiomas admiten la alineación de código que puede recuperar algo de rendimiento en tiempo de ejecución.

¿Cómo podríamos adoptar OOP sin sacrificar el rendimiento?

Aprenda y aplique tanto la POO como los fundamentos.

Es cierto que el cumplimiento estricto de OOP puede impedir que escriba una versión más rápida. A veces, una versión más rápida solo se puede escribir desde cero. Es por eso que ayuda a escribir múltiples versiones de código usando diferentes algoritmos y paradigmas (OOP, genérico, funcional, matemático, spaghetti), y luego usar herramientas de optimización para hacer que cada versión se acerque al rendimiento máximo observado.

¿Hay tipos de código que no se beneficiarán de OOP?

(Ampliado de la discusión entre [@quant_dev], [@ SK-logic] y [@MikeNakis])

  1. Recetas numéricas, que se originan de las matemáticas.
    • Las ecuaciones matemáticas y las transformaciones pueden entenderse como objetos.
    • Se necesitan técnicas de transformación de código muy sofisticadas para generar código ejecutable eficiente. La implementación ingenua ("pizarra") tendrá un rendimiento abismal.
    • Sin embargo, los compiladores principales de hoy en día no pueden hacerlo.
    • El software especializado (MATLAB y Mathematica, etc.) tiene JIT y solucionadores simbólicos capaces de generar código eficiente para algunos subproblemas. Estos solucionadores especializados pueden verse como compiladores de propósito especial (mediadores entre el código legible por humanos y el código ejecutable por máquina) que se beneficiarán de un diseño OOP.
    • Cada subproblema requiere su propio "compilador" y "transformaciones de código". Por lo tanto, esta es un área de investigación abierta muy activa con nuevos resultados que aparecen cada año.
    • Debido a que la investigación lleva mucho tiempo, los escritores de software deben llevar a cabo la optimización en papel y transcribir el código optimizado en el software. El código transcrito podría ser ininteligible.
  2. Código de muy bajo nivel.
      * *
rwong
fuente
8

En realidad no se trata de la orientación a objetos, sino de los contenedores. Si usó una lista de doble enlace para almacenar píxeles en su reproductor de video, sufrirá.

Sin embargo, si usa el contenedor correcto, no hay razón para que un std :: vector sea más lento que una matriz, y dado que ya tiene todos los algoritmos comunes escritos para él, por expertos, es probable que sea más rápido que el código de la matriz de su casa.

Martin Beckett
fuente
1
Debido a que los compiladores son subóptimos (o las reglas del lenguaje de programación prohíben aprovechar ciertas suposiciones u optimizaciones), existe una sobrecarga que no puede eliminarse. Además, ciertas optimizaciones, por ejemplo, la vectorización, tienen requisitos de organización de datos (por ejemplo, estructura de matrices en lugar de matriz de estructuras) que OOP puede mejorar u obstaculizar. (Recientemente trabajé en una tarea de optimización de std :: vector.)
rwong
5

Obviamente, OOP es una buena idea y, como cualquier buena idea, se puede usar en exceso. En mi experiencia, es muy usado. Bajo rendimiento y bajo resultado de mantenimiento.

No tiene nada que ver con la sobrecarga de llamar a funciones virtuales, y no tiene mucho que ver con lo que hace el optimizador / jitter.

Tiene todo que ver con estructuras de datos que, si bien tienen el mejor rendimiento de big-O, tienen factores constantes muy malos. Esto se hace suponiendo que si hay algún problema de limitación de rendimiento en la aplicación, está en otro lugar.

Una forma en que esto se manifiesta es la cantidad de veces por segundo que se realiza una nueva , que se supone que tiene un rendimiento de O (1), pero puede ejecutar de cientos a miles de instrucciones (incluida la eliminación correspondiente o el tiempo de GC). Eso se puede mitigar guardando objetos usados, pero eso hace que el código sea menos "limpio".

Otra forma en que se manifiesta es la forma en que se alienta a las personas a escribir funciones de propiedad, manejadores de notificaciones, llamadas a funciones de clase base, todo tipo de llamadas a funciones subterráneas que existen para tratar de mantener la coherencia. Para mantener la consistencia tienen un éxito limitado, pero tienen un enorme éxito en los ciclos de pérdida. Los programadores entienden el concepto de datos normalizados, pero tienden a aplicarlo solo al diseño de bases de datos. No lo aplican al diseño de la estructura de datos, al menos en parte porque OOP les dice que no tienen que hacerlo. Tan simple como configurar un bit modificado en un objeto puede provocar un tsunami de actualizaciones que se ejecutan a través de la estructura de datos, porque ninguna clase que valga su código toma una llamada modificada y simplemente la almacena .

Tal vez el rendimiento de una aplicación determinada está bien tal como está escrito.

Por otro lado, si hay un problema de rendimiento, aquí hay un ejemplo de cómo hago para ajustarlo. Es un proceso de varias etapas. En cada etapa, alguna actividad en particular representa una gran fracción de tiempo y podría reemplazarse por algo más rápido. (No dije "cuello de botella". Este no es el tipo de cosas que los perfiladores son buenos para encontrar). Este proceso a menudo requiere, para obtener la aceleración, el reemplazo total de la estructura de datos. A menudo, esa estructura de datos está ahí solo porque se recomienda la práctica de OOP.

Mike Dunlavey
fuente
3

En teoría, podría conducir a la lentitud, pero incluso entonces, no sería un algoritmo lento, sería una implementación lenta. En la práctica, la orientación a objetos le permitirá probar varios escenarios hipotéticos (o volver a visitar el algoritmo en el futuro) y, por lo tanto, proporcionarle mejoras algorítmicas , que nunca podría esperar lograr si lo hubiera escrito en forma de espagueti al principio lugar, porque la tarea sería desalentadora. (Esencialmente, tendría que reescribir todo el asunto).

Por ejemplo, al dividir las diversas tareas y entidades para cortar objetos de forma limpia, es posible que pueda ingresar más tarde y, por ejemplo, incrustar una instalación de almacenamiento en caché entre algunos objetos (transparente para ellos) que podría generar mil pliegue de mejora.

En general, los tipos de mejoras que puede lograr mediante el uso de un lenguaje de bajo nivel (o trucos ingeniosos con un lenguaje de alto nivel) proporcionan mejoras de tiempo constantes (lineales), que no figuran en términos de notación big-oh. Con mejoras algorítmicas, puede lograr mejoras no lineales. Eso no tiene precio.

Mike Nakis
fuente
1
+1: la diferencia entre el espagueti y el código orientado a objetos (o código escrito en un paradigma bien definido) es: cada versión de un buen código reescrito brinda una nueva comprensión del problema. Cada versión de spaghetti reescrita nunca aporta ninguna idea.
rwong
@rwong no podría explicarse mejor ;-)
umlcat
3

Pero, ¿podría esta OOP ser una desventaja para el software basado en el rendimiento, es decir, qué tan rápido se ejecuta el programa?

A menudo sí !!! PERO...

En otras palabras, ¿podrían muchas referencias entre muchos objetos diferentes, o el uso de muchos métodos de muchas clases, dar como resultado una implementación "pesada"?

No necesariamente. Esto depende del idioma / compilador. Por ejemplo, un compilador optimizador de C ++, siempre que no use funciones virtuales, a menudo reducirá la sobrecarga de su objeto a cero. Puede hacer cosas como escribir un contenedor sobre un intpuntero inteligente allí o un puntero inteligente sobre un puntero antiguo que funciona tan rápido como el uso directo de estos tipos de datos antiguos.

En otros lenguajes como Java, hay un poco de sobrecarga en un objeto (a menudo bastante pequeño en muchos casos, pero astronómico en algunos casos raros con objetos realmente pequeños). Por ejemplo, Integeres considerablemente menos eficiente que int(toma 16 bytes en lugar de 4 en 64 bits). Sin embargo, esto no es solo un desperdicio descarado o algo por el estilo. A cambio, Java ofrece cosas como la reflexión sobre cada tipo definido por el usuario de manera uniforme, así como la capacidad de anular cualquier función que no esté marcada como final.

Sin embargo, tomemos el mejor de los casos: el compilador optimizador de C ++ que puede optimizar las interfaces de objetos hasta cero sobrecarga. Incluso entonces, la OOP a menudo degradará el rendimiento y evitará que alcance el pico. Eso puede sonar como una paradoja completa: ¿cómo podría ser? El problema radica en:

Diseño de interfaz y encapsulación

El problema es que incluso cuando un compilador puede aplastar la estructura de un objeto a cero sobrecarga (que al menos es muy cierto para optimizar los compiladores de C ++), el diseño de encapsulación e interfaz (y las dependencias acumuladas) de objetos de grano fino a menudo impedirán Representaciones de datos más óptimas para objetos que están destinados a ser agregados por las masas (que a menudo es el caso del software crítico para el rendimiento).

Toma este ejemplo:

class Particle
{
public:
    ...

private:
    double birth;                // 8 bytes
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
    /*padding*/                  // 4 bytes of padding
};
Particle particles[1000000];     // 1mil particles (~24 megs)

Digamos que nuestro patrón de acceso a la memoria es simplemente recorrer estas partículas secuencialmente y moverlas alrededor de cada cuadro repetidamente, rebotando en las esquinas de la pantalla y luego renderizando el resultado.

Ya podemos ver un deslumbrante relleno de 4 bytes por encima requerido para alinear el birthmiembro correctamente cuando las partículas se agregan contiguamente. Ya ~ 16.7% de la memoria se desperdicia con el espacio muerto utilizado para la alineación.

Esto puede parecer discutible porque tenemos gigabytes de DRAM en estos días. Sin embargo, incluso las máquinas más bestiales que tenemos hoy en día solo tienen apenas 8 megabytes cuando se trata de la región más lenta y grande de la caché de la CPU (L3). Cuanto menos podamos encajar allí, más pagaremos en términos de acceso DRAM repetido, y las cosas se volverán más lentas. De repente, perder el 16,7% de la memoria ya no parece un trato trivial.

Podemos eliminar fácilmente esta sobrecarga sin ningún impacto en la alineación del campo:

class Particle
{
public:
    ...

private:
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
};
Particle particles[1000000];     // 1mil particles (~12 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Ahora hemos reducido la memoria de 24 megas a 20 megas. Con un patrón de acceso secuencial, la máquina ahora consumirá estos datos un poco más rápido.

Pero veamos este birthcampo un poco más de cerca. Digamos que registra el tiempo de inicio cuando nace (se crea) una partícula. Imagine que solo se accede al campo cuando se crea una partícula por primera vez, y cada 10 segundos para ver si una partícula debe morir y renacer en una ubicación aleatoria en la pantalla. En ese caso, birthes un campo frío. No se accede a él en nuestros bucles de rendimiento crítico.

Como resultado, los datos críticos de rendimiento real no son 20 megabytes, sino un bloque contiguo de 12 megabytes. ¡La memoria activa real a la que accedemos con frecuencia se ha reducido a la mitad de su tamaño! Espere aceleraciones significativas con respecto a nuestra solución original de 24 megabytes (no es necesario medirla, ya se ha hecho este tipo de cosas miles de veces, pero si tiene dudas, siéntase libre).

Sin embargo, note lo que hicimos aquí. Rompimos por completo la encapsulación de este objeto de partículas. Su estado ahora se divide entre Particlelos campos privados de un tipo y una matriz paralela separada. Y ahí es donde el diseño granular orientado a objetos se interpone en el camino.

No podemos expresar la representación de datos óptima cuando se limita al diseño de la interfaz de un solo objeto muy granular como una sola partícula, un solo píxel, incluso un solo vector de 4 componentes, posiblemente incluso un solo objeto "criatura" en un juego , etc. Se perderá la velocidad de un guepardo si está parado en una pequeña isla de 2 metros cuadrados, y eso es lo que a menudo hace el diseño orientado a objetos muy granular en términos de rendimiento. Limita la representación de datos a una naturaleza subóptima.

Para llevar esto más lejos, digamos que dado que solo estamos moviendo partículas, podemos acceder a sus campos x / y / z en tres bucles separados. En ese caso, podemos beneficiarnos de las intrínsecas SIMD de estilo SoA con registros AVX que pueden vectorizar 8 operaciones SPFP en paralelo. Pero para hacer esto, ahora debemos usar esta representación:

float particle_x[1000000];       // 1mil particle X positions (~4 megs)
float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Ahora estamos volando con la simulación de partículas, pero mira lo que sucedió con nuestro diseño de partículas. Se ha demolido por completo, y ahora estamos viendo 4 matrices paralelas y ningún objeto para agregarlas en absoluto. Nuestro Particlediseño orientado a objetos se ha vuelto sayonara.

Esto me sucedió muchas veces trabajando en campos críticos para el rendimiento donde los usuarios exigen velocidad, y solo la corrección es lo único que exigen más. Estos pequeños diseños orientados a objetos pequeños tuvieron que ser demolidos, y las roturas en cascada a menudo requerían que usáramos una estrategia de desaprobación lenta hacia el diseño más rápido.

Solución

El escenario anterior solo presenta un problema con los diseños granulares orientados a objetos. En esos casos, a menudo terminamos teniendo que demoler la estructura para expresar representaciones más eficientes como resultado de repeticiones de SoA, división de campo caliente / frío, reducción de relleno para patrones de acceso secuencial (el relleno a veces es útil para el rendimiento con acceso aleatorio patrones en casos de AoS, pero casi siempre un obstáculo para los patrones de acceso secuencial), etc.

Sin embargo, podemos tomar esa representación final en la que nos decidimos y aún modelar una interfaz orientada a objetos:

// Represents a collection of particles.
class ParticleSystem
{
public:
    ...

private:
    double particle_birth[1000000];  // 1mil particle births (~8 bytes)
    float particle_x[1000000];       // 1mil particle X positions (~4 megs)
    float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
    float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
};

Ahora estamos bien. Podemos obtener todas las golosinas orientadas a objetos que nos gustan. El guepardo tiene un país entero para cruzar tan rápido como pueda. Nuestros diseños de interfaz ya no nos atrapan en una esquina de cuello de botella.

ParticleSystempotencialmente puede incluso ser abstracto y usar funciones virtuales. Ahora es discutible, estamos pagando los gastos generales en el nivel de recolección de partículas en lugar de en un nivel por partícula . La sobrecarga es 1 / 1,000,000 de lo que sería de lo contrario si estuviéramos modelando objetos a nivel de partícula individual.

Así que esa es la solución en áreas verdaderamente críticas para el rendimiento que manejan una carga pesada, y para todo tipo de lenguajes de programación (esta técnica beneficia a C, C ++, Python, Java, JavaScript, Lua, Swift, etc.). Y no se puede etiquetar fácilmente como "optimización prematura", ya que esto se relaciona con el diseño y la arquitectura de la interfaz . No podemos escribir una base de código que modele una sola partícula como un objeto con una gran cantidad de dependencias del cliente en unParticle'sinterfaz pública y luego cambiar de opinión más tarde. Lo he hecho mucho cuando se me llama para optimizar las bases de código heredadas, y eso puede terminar tomando meses de reescribir cuidadosamente decenas de miles de líneas de código para usar el diseño más voluminoso. Esto idealmente afecta cómo diseñamos las cosas por adelantado, siempre que podamos anticipar una carga pesada.

Sigo haciendo eco de esta respuesta de una forma u otra en muchas preguntas de rendimiento, y especialmente en aquellas relacionadas con el diseño orientado a objetos. El diseño orientado a objetos aún puede ser compatible con las necesidades de rendimiento de mayor demanda, pero tenemos que cambiar un poco la forma en que pensamos al respecto. Tenemos que darle a ese guepardo algo de espacio para que corra lo más rápido posible, y eso a menudo es imposible si diseñamos pequeños objetos que apenas almacenan ningún estado.


fuente
Fantástico. Esto es lo que realmente estaba buscando en términos de combinar OOP con una demanda de alto rendimiento. Realmente no puedo entender por qué no se ha votado más.
pbx
2

Sí, la mentalidad orientada a objetos definitivamente puede ser neutral o negativa cuando se trata de programación de alto rendimiento, tanto a nivel algorítmico como de implementación. Si OOP reemplaza el análisis algorítmico, puede llevarlo a una implementación prematura y, en el nivel más bajo, las abstracciones de OOP deben dejarse de lado.

El problema surge del énfasis de OOP en pensar en instancias individuales. Creo que es justo decir que la forma de pensar de un OOP sobre un algoritmo es pensar en un conjunto específico de valores e implementarlo de esa manera. Si ese es su camino de más alto nivel, es poco probable que se dé cuenta de una transformación o reestructuración que conduzca a grandes ganancias de O.

A nivel algorítmico, a menudo se piensa en el panorama más amplio y en las restricciones o relaciones entre los valores que conducen a grandes ganancias de O. Un ejemplo podría ser que no hay nada en la mentalidad de OOP que lo lleve a transformar "sumar un rango continuo de enteros" de un ciclo a(max + min) * n/2

En el nivel de implementación, aunque las computadoras son "lo suficientemente rápidas" para la mayoría de los algoritmos de nivel de aplicación, en el código de bajo nivel de rendimiento crítico uno se preocupa mucho por la localidad. Nuevamente, el énfasis de OOP en pensar en una instancia individual y los valores de un paso a través del ciclo pueden ser negativos. En el código de alto rendimiento, en lugar de escribir un bucle directo, es posible que desee desenrollar parcialmente el bucle, agrupar varias instrucciones de carga en la parte superior, luego transformarlas en un grupo y luego escribirlas en un grupo. Todo el tiempo estarías prestando atención a los cálculos intermedios y, enormemente, al acceso a la memoria caché y; problemas donde las abstracciones OOP ya no son válidas. Y, si se sigue, puede ser engañoso: en este nivel, debe conocer y pensar sobre las representaciones a nivel de máquina.

Cuando observa algo como las primitivas de rendimiento de Intel, tiene literalmente miles de implementaciones de la Transformada rápida de Fourier, cada una ajustada para que funcione mejor para un tamaño de datos específico y una arquitectura de máquina. (Fascinantemente, resulta que la mayor parte de estas implementaciones son generadas por la máquina: Markus Püschel Automatic Performance Programming )

Por supuesto, como la mayoría de las respuestas han dicho, para la mayoría del desarrollo, para la mayoría de los algoritmos, la POO es irrelevante para el rendimiento. Mientras no esté "pesimizando prematuramente" y agregando muchas llamadas no locales, el thispuntero no está ni aquí ni allá.

Larry OBrien
fuente
0

Está relacionado, y a menudo se pasa por alto.

No es una respuesta fácil, depende de lo que quieras hacer.

Algunos algoritmos son mejores en rendimiento usando programación estructurada simple, mientras que otros son mejores usando orientación a objetos.

Antes de la orientación a objetos, muchas escuelas enseñan el diseño de algoritmos (ed) con programación estructurada. Hoy en día, muchas escuelas enseñan programación orientada a objetos, ignorando el diseño y el rendimiento de algoritmos.

Por supuesto, allí donde las escuelas que enseñan programación estructurada, que no se preocupaban por los algoritmos, en absoluto.

umlcat
fuente
0

El rendimiento se reduce a los ciclos de CPU y memoria al final. Pero la diferencia porcentual entre la sobrecarga de mensajes y encapsulación de OOP y una semántica de programación más abierta puede o no ser un porcentaje lo suficientemente significativo como para marcar una diferencia notable en el rendimiento de su aplicación. Si una aplicación está unida al disco o a la memoria caché de datos, cualquier sobrecarga de OOP puede perderse completamente en el ruido.

Pero, en los bucles internos del procesamiento de señales e imágenes en tiempo real y otras aplicaciones de cómputo numérico, la diferencia puede ser un porcentaje significativo de los ciclos de CPU y memoria, lo que puede hacer que la sobrecarga de OOP sea mucho más costosa de ejecutar.

La semántica de un lenguaje OOP en particular puede o no exponer suficientes oportunidades para que el compilador optimice esos ciclos, o para que los circuitos de predicción de rama de la CPU siempre adivinen correctamente y cubran esos ciclos con pre-captación y canalización.

hotpaw2
fuente
0

Un buen diseño orientado a objetos me ayudó a acelerar considerablemente una aplicación. A tuvo que generar gráficos complejos de forma algorítmica. Lo hice a través de la automatización de Microsoft Visio. Trabajé, pero fue increíblemente lento. Afortunadamente, había insertado un nivel adicional de abstracción entre la lógica (el algoritmo) y las cosas de Visio. Mi componente Visio expuso su funcionalidad a través de una interfaz. Esto me permitió reemplazar fácilmente el componente lento con otro que crea archivos SVG, ¡eso fue al menos 50 veces más rápido! Sin un enfoque orientado a objetos limpio, los códigos para el algoritmo y el control de la Visión se habrían enredado de alguna manera, lo que habría convertido el cambio en una pesadilla.

Olivier Jacot-Descombes
fuente
¿Quiso decir OO Design aplicado con un lenguaje de procedimiento, o OO Design y OO lenguaje de programación?
umlcat
Estoy hablando de una aplicación C #. Tanto el diseño como el lenguaje son OO Mientras que la OO-iness del lenguaje introducirá algunos pequeños éxitos de rendimiento (llamadas a métodos virtuales, creación de objetos, acceso de miembros a través de la interfaz), el diseño OO me ayudó a crear una aplicación mucho más rápida. Lo que quiero decir es: Olvida los éxitos de rendimiento debido a OO (lenguaje y diseño). A menos que esté haciendo cálculos pesados ​​con millones de iteraciones, OO no lo dañará. Donde generalmente pierdes mucho tiempo es E / S.
Olivier Jacot-Descombes