¿Qué hace que un producto de software grande y complejo sea lento? [cerrado]

16

Por una razón que es en gran medida irrelevante, instalé Delphi 7 una vez más en mucho tiempo. Tengo que decir que me quedé completamente impresionado, de una manera que no he estado en bastante tiempo. No es así como recuerdo las cosas. La instalación tomó alrededor de 30 segundos. El lanzamiento tardó 2 segundos, y fue inmediatamente utilizable. Puedo presionar "Ejecutar" el segundo después de que comenzó, y menos de un segundo después el programa en blanco ya está visible y ejecutándose. ¡Hurra por que las computadoras sean mucho más rápidas!

Pero la razón por la que me ha impresionado es porque usualmente uso Visual Studio 2010, eso no se siente tan ágil en absoluto. Por supuesto, Delphi 7 es un sistema mucho más pequeño que Visual Studio 2010, pero tiene la apariencia de tener todas las cosas realmente necesarias allí: una paleta de control, un diseñador de formularios, un editor de código con la finalización de código. Me doy cuenta de que el lenguaje puede ser más simple, y la finalización del código puede ser mucho menos potente, y el IDE puede no ser tan extensible y rico en funciones, pero aún así: no entiendo cómo (es decir, a través de qué mecanismo) tiene muchas características adicionales (que quizás aún no haya activado) hacen que un sistema como Visual Studio siempre se sienta lento en comparación.

Me gustaría preguntar a las personas con experiencia en el trabajo con sistemas la escala de Visual Studio: ¿qué es lo que los hace lentos? ¿Son las capas sobre capas de abstracciones necesarias para mantener la base de código dentro de las capacidades de comprensión humana? ¿Es la gran cantidad de código que debe ejecutarse? ¿Es la tendencia moderna hacia enfoques de ahorro de tiempo del programador a un costo (alucinantemente enorme) en el departamento de ciclos de reloj / uso de memoria?

Roman Starkov
fuente
77
Simple: a medida que aumenta la masa, se requiere más fuerza para vencer la inercia.
Shog9
Alguien me dijo una vez gerentes pero no lo creo en absoluto.
MIchael Grassman
1
Esta es una gran parte de la razón por la que todavía uso principalmente D7 para la programación de Delphi.
GrandmasterB
El código más rápido es el que nunca se ejecuta.
Henry
44
@romkyns: Encuentro que mucho software en la era moderna a menudo es increíblemente hinchado, innecesariamente grande y difícil de manejar. Gran parte del software ahora resuelve los mismos problemas que se resolvieron diez, incluso hace veinte años, con una fracción del poder y el espacio. ¿Por qué sigue tan atrasado como siempre, si no más? Ineficiencia e hinchazón.
Orbling

Respuestas:

20

Astronáutica Arquitectónica

Visual Studio 2010 se basa en Windows Presentation Foundation. Eche un vistazo a la clase Button para WPF. Es el noveno hijo de una clase base. Tiene alrededor de 5 páginas de propiedades, métodos y eventos. Detrás de escena tiene otras cinco páginas de definiciones de estilo que describen sus esquinas bellamente redondeadas y las sutiles transiciones de animación cuando un cursor del mouse se mueve sobre él. Esto es todo por algo que fundamentalmente muestra texto o una imagen y produce un evento de clic cuando detecta que se está presionando un botón del mouse.

Detenga un programa como Visual Studio en cualquier punto aleatorio. Mira el rastro de la pila. Es muy probable que tenga 20 niveles de profundidad en la pila de llamadas y que se hayan cargado cinco DLL para llegar allí.

Ahora, compara estas dos cosas con Delphi. Apuesto a que encuentra que un botón de Delphi tiene solo 20 propiedades, métodos y eventos. Apuesto a que el IDE de Delphi solo tiene un seguimiento de pila de 5-7 niveles de profundidad. Porque cuando las computadoras eran más lentas, simplemente no podía tomar la sobrecarga de Visual Studio 2010 sin que el IDE tomara 40 minutos para comenzar :-)

¿Es uno mejor que el otro? Bueno, generalmente puedo decirle a un programa Delphi cuando se carga porque se ve plano, los colores están silenciados (¿8 bits quizás?), Y no hay sombras o animaciones sutiles. Me siento "barato" en estos días. Barato, pero rápido.

¿Estamos mejor? Esa es una pregunta para los filósofos, no para los codificadores.

Jay Beavers
fuente
44
Un programa de Delphi no parece plano. Más bien, un programador programa un programa para parecer plano. Puede hacer interfaces agradables, modernas y a todo color con Delphi tal como podría hacerlo en C # o C ++.
GrandmasterB
2
Esta es una respuesta perspicaz; Pero no estoy seguro de que esté completo. Visual Studio 2008 (el predecesor de 2010) no tiene WPF y sigue siendo mucho más lento que Delphi 7. ¿Seguiría diciendo lo mismo sobre la profundidad de la pila de llamadas y la cantidad de archivos DLL cargados?
Timwi
3
@Timwi Sí, absolutamente lo haría. Mi punto era menos sobre los males de WPF (en realidad me gusta WPF) y más sobre cómo tendemos a agregar capas sobre capas de abstracción de software cuando se nos da la opción. Quizás Visual Studio 2008 no tuvo tanta sobrecarga, pero como notó, tuvo bastante :-)
Jay Beavers
@GrandmasterB, no estoy criticando a Delphi porque viene con menos suposiciones y bibliotecas más simples. WPF fue diseñado asumiendo que la aceleración de hardware de la GPU permitiría a los programas usar colores más profundos, animaciones frecuentes, mezclas alfa, sombras, etc. Delphi fue diseñado en un momento en que estos supuestos no podían hacerse. ¿Podrías volver a implementar todo esto en Delphi? Claro, pero tendrías que poner mucha codificación solo para obtener el comportamiento de un botón WPF. En el lado positivo, un botón Delphi no viene con los requisitos de CPU, memoria y GPU que tiene un botón WPF, que era la pregunta del @ OP.
Jay Beavers
10
Su argumento para una interfaz de usuario plana y simple está completamente invalidado por la nueva interfaz de usuario 'moderna' de Windows 10. Ahora tenemos toda esa sobrecarga para crear botones planos, cuadrados y lisos como los que teníamos hace 30 años.
gbjbaanb
11

Me gustaría preguntar a las personas con experiencia en el trabajo con sistemas la escala de Visual Studio: ¿qué es lo que los hace lentos? ¿Son las capas sobre capas de abstracciones necesarias para mantener la base de código dentro de las capacidades de comprensión humana? ¿Es la gran cantidad de código que debe ejecutarse? ¿Es la tendencia moderna hacia enfoques de ahorro de tiempo del programador a un costo (alucinantemente enorme) en el departamento de ciclos de reloj / uso de memoria?

Creo que adivinó varios de ellos, pero me gustaría ofrecer lo que considero el factor más importante, después de haber trabajado en una base de código razonablemente grande (no estoy seguro de si es tan grande como Visual Studio), estaba en los millones de líneas de código categoría y alrededor de mil complementos) durante aproximadamente 10 años y se producen fenómenos de observación.

También es un poco menos controvertido ya que no entra en las API o las características del lenguaje ni nada de eso. Esos se relacionan con "costos" que pueden generar un debate en lugar de "gastos", y quiero centrarme en "gastos".

Coordinación suelta y legado

Lo que observé es que la falta de coordinación y un largo legado tienden a generar una gran cantidad de desechos acumulados.

Por ejemplo, encontré alrededor de cien estructuras de aceleración en esta base de código, muchas de ellas redundantes.

Tendríamos un árbol KD para acelerar un motor de física, otro para un nuevo motor de física que a menudo se ejecutaba en paralelo con el anterior, tendríamos docenas de implementaciones de octrees para varios algoritmos de malla, otro árbol KD para renderizar , selección, etc., etc., etc. Estas son estructuras de árbol grandes y voluminosas que se utilizan para acelerar las búsquedas. Cada individuo puede llevar cientos de megabytes a gigabytes de memoria para una entrada de tamaño muy promedio. No siempre se crearon instancias y se usaron todo el tiempo, pero en un momento dado, 4 o 5 de ellos podrían estar en la memoria simultáneamente.

Ahora todos estos almacenaban exactamente los mismos datos para acelerar la búsqueda de ellos. Puede imaginarlo como la antigua base de datos analógica que almacena todos sus campos en 20 mapas / diccionarios / árboles B + redundantes diferentes a la vez, organizados de forma idéntica por las mismas teclas, y los busca todo el tiempo. Ahora estamos tomando 20 veces la memoria y el procesamiento.

Además, debido a la redundancia, hay poco tiempo para optimizar cualquiera de ellos con el precio de mantenimiento que viene con eso, e incluso si lo hiciéramos, solo tendría el 5% del efecto que idealmente tendría.

¿Qué causa este fenómeno? La falta de coordinación fue la causa número uno que vi. Muchos miembros del equipo a menudo trabajan en sus ecosistemas aislados, desarrollando o utilizando estructuras de datos de terceros, pero no utilizan las mismas estructuras que otros miembros del equipo estaban utilizando, incluso si eran directamente duplicados evidentes de las mismas preocupaciones.

¿Qué hace que este fenómeno persista? El legado y la compatibilidad fue la causa número uno que vi. Como ya pagamos el costo de implementar estas estructuras de datos y grandes cantidades de código dependían de estas soluciones, a menudo era demasiado arriesgado intentar consolidarlas en menos estructuras de datos. A pesar de que muchas de estas estructuras de datos eran altamente redundantes conceptualmente, no siempre estaban cerca de ser idénticas en sus diseños de interfaz. Por lo tanto, reemplazarlos habría sido un cambio grande y arriesgado en lugar de simplemente dejarles consumir memoria y tiempo de procesamiento.

Eficiencia de memoria

Por lo general, el uso de la memoria y la velocidad tienden a estar relacionados al nivel masivo al menos. A menudo puede detectar software lento por cómo está acumulando memoria. No siempre es cierto que más memoria conduzca a una desaceleración, ya que lo que importa es la memoria "activa" (a qué memoria se accede todo el tiempo, si un programa usa una gran cantidad de memoria pero solo se usa 1 megabyte de todo el tiempo). tiempo, entonces no es un gran problema en cuanto a velocidad).

Por lo tanto, puede detectar los potenciales cerdos en función del uso de memoria la mayor parte del tiempo. Si una aplicación requiere decenas a cientos de megabytes de memoria en el inicio, probablemente no será muy eficiente. Decenas de megabytes pueden parecer pequeños cuando tenemos gigabytes de DRAM en estos días, pero los cachés de CPU más grandes y lentos todavía están en el rango de megabytes miserables, y los más rápidos todavía están en el rango de kilobytes. Como resultado, un programa que usa 20 megabytes solo para iniciarse y no hacer nada en realidad todavía está usando bastante "mucha" memoria desde el punto de vista de la caché de la CPU del hardware, especialmente si se accede a los 20 megabytes de esa memoria repetidamente y con frecuencia a medida que se ejecuta el programa.

Solución

Para mí, la solución es buscar equipos más pequeños y coordinados para crear productos, que puedan hacer un seguimiento de sus "gastos" y evitar "comprar" los mismos artículos una y otra vez.

Costo

Me sumergiré en el lado del "costo" más controvertido solo un poco con un fenómeno de "gasto" que he observado. Si un lenguaje termina con un precio inevitable para un objeto (como uno que proporciona reflexión en tiempo de ejecución y no puede forzar la asignación contigua para una serie de objetos), ese precio solo es costoso en el contexto de un elemento muy granular, como un soltero Pixelo Boolean.

Sin embargo, veo un montón de código fuente para los programas que hacen manejar una carga pesada (por ejemplo: se trata de cientos de miles a millones de Pixelo Booleancasos) pagar ese costo a un nivel tan granular.

La programación orientada a objetos puede exacerbar eso. Sin embargo, no es el costo de los "objetos" per se o incluso la OOP la culpa, es simplemente que dichos costos se pagan a un nivel tan granular de un elemento pequeño que se creará por millones.

Así que ese es el otro fenómeno de "costo" y "gasto" que estoy observando. El costo es de centavos, pero los centavos se suman si estamos comprando un millón de latas de refresco individualmente en lugar de negociar con un fabricante para una compra a granel.

La solución aquí para mí es la compra "a granel". Los objetos están perfectamente bien incluso en idiomas que tienen un precio de centavos para cada uno, siempre que este costo no se pague individualmente un millón de veces por el equivalente analógico de una lata de refresco.

Optimización prematura

Nunca me gustó la redacción que Knuth usó aquí, porque la "optimización prematura" rara vez hace que los programas de producción del mundo real sean más rápidos. Algunos interpretan eso como "optimizar temprano" cuando Knuth se refería más a "optimizar sin el conocimiento / experiencia adecuada para conocer su verdadero impacto en el software". En todo caso, el efecto práctico de la verdadera optimización prematura a menudo hará que el software sea más lento , ya que la degradación en la capacidad de mantenimiento significa que hay poco tiempo para optimizar las rutas críticas que realmente importan .

Este es el fenómeno final que observé, donde los desarrolladores que intentaban ahorrar centavos en la compra de una sola lata de refresco, nunca más para comprar, o peor aún, una casa, perdían todo su tiempo pellizcando centavos (o peor, centavos imaginarios de no entender su compilador o la arquitectura del hardware) cuando se gastaron miles de millones de dólares en otros lugares.

El tiempo es muy limitado, por lo que tratar de optimizar los absolutos sin tener la información contextual adecuada a menudo nos priva de la oportunidad de optimizar los lugares que realmente importan y, por lo tanto, en términos de efecto práctico, diría que "la optimización prematura hace que el software sea mucho más lento". "

El problema es que hay tipos de desarrolladores que tomarán lo que escribí anteriormente sobre los objetos y tratarán de establecer un estándar de codificación que prohíba la programación orientada a objetos o algo así de loco. La optimización efectiva es una priorización efectiva, y no tiene ningún valor si nos estamos ahogando en un mar de problemas de mantenimiento.

Thomas Owens
fuente
2
Deuda técnica, en otras palabras. Deuda técnica que nunca se paga.
Robert Harvey
1
Robert tiene razón. Un error de un tipo, doscientos errores --forcepor parte de los gerentes gritando "serás despedido si no implementas esto para mañana" que destruirá años de buenas prácticas de ingeniería de software, TDD, pruebas unitarias y cualquier principio de programación humano y sensato. , además de otras dos veces que estabas cansado ... ese tipo que dejó enojado a la compañía porque lo despidieron sin razón y estropeó la base de código ... esas bibliotecas descontinuadas que nunca actualizaste ... y aquí lo tienes: deliciosa base de código de espagueti y software hinchado. Buen
provecho
2
Interesante, especialmente en cómo has visto mal uso de granularidad excesiva. Me sorprendí haciendo algo similar en ocasiones en el pasado y como resultado obtuve un rendimiento pobre. Esto es bastante similar a su respuesta de hace unos días sobre el uso de colecciones y algoritmos masivos con preferencia sobre la granularidad excesiva . No puedo creer que esa respuesta no fuera más apreciada por su profundidad. Me hace repensar varios de los diseños que he construido a lo largo de los años. Me pregunto por qué esas técnicas no se promueven más ampliamente.
Mike apoya a Mónica el
2
@ Mike, estoy un poco roto en lo que respecta a tratar de promover una mentalidad más orientada a los datos. Es popular en la industria de los juegos donde intentan utilizar cada pulgada del hardware. Dicho esto, es cierto que reduce la flexibilidad. Si tiene una clase de píxeles abstractos, puede hacer locuras con eso como tener una sola imagen que mezcle dos o más formatos de píxeles diferentes. Sin embargo, cuando tratamos con rutas críticas, probablemente ninguna imagen se beneficiaría de ese nivel de flexibilidad, y el rendimiento comienza a convertirse en una preocupación real con cualquier cosa que involucre imágenes y píxeles.
1
En los viejos tiempos, implementé un código para omitir las API de gráficos y acceder directamente a los píxeles en la memoria para una parte crítica de mi código. La diferencia entre las muchas capas de abstracción y acceso directo era algo así como 100x, lo que importaba en una computadora en esos días. Ahora, sus computadoras son lo suficientemente rápidas para que pueda analizar cualquier cantidad de abstracción, si es necesario.
Michael Shopsin