¿Por qué debería importarme el micro rendimiento y la eficiencia?


71

Muchas preguntas y respuestas en las páginas C / C ++, discuten específica o indirectamente problemas de micro rendimiento (como la sobrecarga de una función indirecta vs directa vs en línea), o usando un algoritmo O (N 2 ) vs O (N log N) en Una lista de 100 artículos.

Siempre codifico sin preocuparme por el rendimiento micro y con poca preocupación por el rendimiento macro, centrándome en un código fácil de mantener y confiable, a menos que sepa que tengo un problema.

Mi pregunta es ¿por qué una gran cantidad de programadores se preocupan tanto? ¿Es realmente un problema para la mayoría de los desarrolladores? ¿He tenido la suerte de no tener que preocuparme demasiado por ello o soy un mal programador?


55
+1, buena pregunta general.
iammilind

+1 buena pregunta ... agregué 2 etiquetas ... espero que no te importe eso.

2
Encabezo dos grandes citas 1) "La optimización prematura es la raíz de todo mal". 2) Se empleará el 80% de su tiempo del 20% de su código (regla 80/20).
James Khoury

2
Noto que un par de respuestas hablan sobre mi ejemplo O (n * n). Especifiqué explícitamente una lista de 100 elementos, pero aún insisten en que el O (nlogn) es mejor, indicando explícitamente mejoras de rendimiento si la lista, en el futuro, va a 1000 o millones. ¿Es esta obsesión por la micro optimización porque los programadores están programando para posibles requisitos futuros en lugar de requisitos actuales reales? (¿Dónde he escuchado eso antes?)
Mattnz

55
@James, la cita completa de Donald Knuth es "Debemos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal". Habrá algunas buenas respuestas sobre el 3% restante en este hilo.
StuperUser

Respuestas:


14

En la práctica, el rendimiento rara vez es un problema que debe gestionarse con ese nivel de detalle. Vale la pena vigilar la situación si sabe que va a almacenar y manipular grandes cantidades de datos, pero de lo contrario, tiene razón, y mejor aún, manteniendo las cosas simples.

Una de las trampas más fáciles de caer, especialmente en C y C ++ donde tienes un control tan preciso, es optimizar demasiado pronto y a un nivel demasiado fino. En general, la regla es: A) no optimice hasta que descubra que tiene un problema, y ​​B) no optimice nada que no haya demostrado ser un área problemática utilizando un generador de perfiles.

Un corolario de B) es: los programadores son notoriamente malos para predecir dónde están sus cuellos de botella de rendimiento, aunque, a uno, piensan que son buenos en eso. Use un generador de perfiles y optimice las partes que son lentas, o cambie los algoritmos si una sección de código se llama demasiadas veces, de modo que esté causando un problema.


66
Otro: el código de inicialización que se ejecuta una vez generalmente no necesita optimización, así que busque en otro lado.
Mike DeSimone

3
Depende de con qué frecuencia es "una vez". Cuando corro ./configure, me atrevería a decir que hasta el 75% del tiempo de ejecución podría dedicarse al código de "inicialización" en los programas que ejecuta el script. El 25-50% incluso podría gastarse en enlaces dinámicos.
R ..

12
La regla A es una regla terrible. La arquitectura de un sistema desempeña un papel en el rendimiento, y si descubres más tarde que tu arquitectura simplemente no puede soportar tus requisitos de rendimiento, básicamente estás jodido. Entonces, si bien puede pasar por alto detalles, ignorar esto completamente al principio es simplemente incorrecto.
edA-qa mort-ora-y

3
@ edA-qa: Solía ​​pensar que sí, pero a lo largo de los años he experimentado muchos más proyectos de lucha o fracaso antes de que las consideraciones de rendimiento se convirtieran en una preocupación. Cada vez que he tenido problemas de rendimiento, la solución ha sido un costo relativamente bajo, días o unas pocas semanas. No hay más preocupación que cualquier otro "error" detectado y solucionado. Sin embargo, como cualquier otro elemento de riesgo, los resultados de rendimiento deben identificarse y mitigarse al inicio del proyecto.
mattnz

55
El OP preguntó por qué a tantos les importa, y no veo cómo esta respuesta realmente respondió la pregunta, a menos que el OP estuviera más interesado en escuchar a alguien decir "¡no te preocupes por eso!".
rojo-tierra

54

Creo que todo en su lista es micro-optimización, que generalmente no debería considerarse, excepto

usando un algoritmo O (n * n) vs O (NlogN) en una lista de 100 elementos

que creo que debería ser mirado. Claro, esa lista es de 100 elementos en este momento, y todo es rápido para una n pequeña , pero estaría dispuesto a apostar pronto que el mismo código se reutilizará para una lista de varios millones de líneas, y el código seguirá teniendo para trabajar razonablemente.

Elegir el algoritmo correcto nunca es una microoptimización. Nunca se sabe qué tipo de datos se utilizará ese mismo código durante dos meses o dos años después. A diferencia de las "micro optimizaciones", que son fáciles de aplicar con la guía de un generador de perfiles, los cambios en los algoritmos a menudo requieren un rediseño significativo para hacer un uso efectivo de los nuevos algoritmos. (Por ejemplo, algunos algoritmos requieren que los datos de entrada ya estén ordenados, lo que podría obligarlo a modificar partes significativas de sus aplicaciones para garantizar que los datos permanezcan ordenados)


36
+1 para "Elegir el algoritmo correcto nunca es una microoptimización".

9
También hice +1, pero tenga en cuenta que elegir el algoritmo óptimo de big-O cuando sus tamaños de datos seguramente serán pequeños puede ser perjudicial para el tiempo de desarrollo, el tamaño del programa e incluso el uso de memoria. Si está ordenando manos de póker, ¿realmente desea escribir una clasificación rápida, ordenada o combinada? Comenzaría con una simple inserción de clasificación o usaría una red de clasificación.
R ..

8
Eso es gracioso. En un hilo sobre micro-optimización, muchos comentaristas micro-optimizan las respuestas. ;)
Seguro

55
"Estaría dispuesto a apostar pronto que el mismo código se reutilizará para una lista de varios millones de líneas": eso depende completamente del dominio del problema. Ejemplos: si está escribiendo un algoritmo de ajedrez, puede estar razonablemente seguro de que el tamaño del tablero no cambiará. Si programa un vehículo autónomo, la cantidad de ruedas tampoco crecerá tan rápido.
nikie

3
No me gusta "elegir el algoritmo correcto nunca es una microoptimización" porque es OBVIAMENTE cierto, dada la naturaleza de la palabra "correcto". Sin embargo, creo que su implicación es realmente el algoritmo "más rápido o más eficiente", con lo que no estoy de acuerdo. Elegir el algoritmo más eficiente es la elección incorrecta si lleva mucho tiempo implementarlo y la velocidad o el espacio de ese segmento difícilmente importa.
Casey Patton

18

Hace muuuucho tiempo atrás, en mi primer trabajo, escribí código para sistemas embebidos. Estos sistemas usaban microprocesadores 8086 y tenían memoria limitada. Utilizamos el compilador Intel C. Un sistema que construí necesitaba acceder a una matriz tridimensional de estructuras. Lo construí tal como me dijo el libro: llame a malloc para las 3 dimensiones, luego asigne filas para la siguiente dimensión, luego calloc para los nodos finales.

Fue bastante complicado (para mí en ese momento), tuve que hacer un ajuste de curva, control de proceso ANOVA y análisis Chi-cuadrado. No había bibliotecas que hicieran esto por nosotros; tuvimos que escribirlo todo y adaptarlo al 8086.

El sistema funcionaba como un perro. Después de un perfil rápido, descubrí que uno de los mayores problemas era el asignador. Para solucionar el problema, abandoné todas las llamadas a malloc e hice mi propia administración de memoria de un gran bloque de memoria.


En otro caso en el mismo trabajo, el cliente se quejaba del tiempo de respuesta en su sistema de control de proceso estadístico. El equipo que tenía delante había diseñado un sistema de "software PLC" donde los operadores podían usar una lógica booleana para combinar señales e interruptores de disparo. Lo escribieron en un lenguaje simplificado, lo que hoy llamaríamos un "lenguaje específico de dominio". según recuerdo, parecía ((A1 + B1) > 4) AND (C1 > C2)y así sucesivamente.

El diseño original analizaba e interpretaba esa cadena cada vez que se evaluaba. En nuestro miserable procesador, esto consumió mucho tiempo y significó que el controlador del proceso no podía actualizarse tan rápido como el proceso se estaba ejecutando.

Le eché un nuevo vistazo y decidí que podía traducir esa lógica en código de ensamblaje, en tiempo de ejecución. Lo analicé una vez y luego, cada vez que se ejecutaba, la aplicación llamaba a una función generada dinámicamente. Algo así como algunos virus hacen hoy, supongo (pero no lo sé). El resultado fue un aumento de 100 veces en el rendimiento, lo que hizo que el cliente y mi jefe estuvieran realmente felices.

El nuevo código no era tan fácil de mantener, ya que había creado un compilador personalizado . Pero la ventaja de rendimiento superó con creces la desventaja de mantenimiento.


Más recientemente, estaba trabajando en un sistema que necesitaba analizar un vuelo XML, dinámicamente. Los archivos más grandes tomarían mucho más tiempo. Esto fue muy sensible al rendimiento; un análisis demasiado lento provocaría que la interfaz de usuario se vuelva completamente inutilizable.

Este tipo de cosas surgen todo el tiempo.


Entonces ... a veces quieres un código fácil de escribir que se pueda mantener. A veces quieres un código que se ejecute rápidamente. La compensación es la decisión de ingeniería que debe tomar en cada proyecto.


9
En todos sus ejemplos, el costo de optimizarlo después no fue mucho más alto que escribir el código rápido desde el principio. Por lo tanto, escribir primero un código más lento y simple y luego optimizarlo cuando fue necesario funcionó bien en todos ellos.
CodesInChaos

66
@CodeInChaos: la respuesta no dice lo contrario. Habla de la pregunta del OP "¿Por qué debería importarme el micro rendimiento y la eficiencia?" Los problemas previos a la optimización simplemente fueron inferidos por los otros respondedores.
webbiedave

12

Si está procesando imágenes grandes e iterando sobre cada píxel, el ajuste del rendimiento puede ser crítico.


2
+1 - también, financiación de alta frecuencia, cualquier tipo de codificador / decodificador de audio / video, simulaciones y modelado (por ejemplo, juegos), bits de todo el sistema como programadores de CPU y administradores de memoria, etc.
Billy ONeal

3
PUEDE ser crítico, pero ES solo crítico después de que haya demostrado que es así y lo haya perfilado para que sea donde cree que está el problema. (Sugerencia: probablemente no esté allí.)
SOLO MI OPINIÓN correcta

2
@ SOLO MI OPINIÓN correcta: en realidad, para el procesamiento de imágenes, el procesamiento de datos suele ser el segundo mayor consumidor de tiempo (E / S sigue siendo el mayor). Sin embargo, la optimización para E / S requiere muchos diseños inusuales / locos y su aceptación por parte de otros programadores, y a veces es absolutamente imposible de mejorar. Sin embargo, la parte de procesamiento suele ser vergonzosamente paralelizable, por lo tanto, son beneficios fácilmente alcanzables. (El ajuste de uno puede ser visto por otro como una implementación de libro de texto directo ... a menos que alcance el nivel de VirtualDub)
rwong

12

Déjame contarte un poco sobre por qué detrás de la cultura.

Si está más cerca de los 40 que de los 20, y ha estado programando para ganarse la vida durante su edad adulta, entonces llegó a la mayoría de edad cuando C ++ era realmente el único juego en la ciudad, las aplicaciones de escritorio eran la norma y el hardware todavía era software muy rezagado en términos de ancho de banda / capacidades de rendimiento.

  • Solíamos tener que hacer trucos estúpidos de programación para poder leer archivos grandes (> 2G) ...
  • Solíamos preocuparnos por el tamaño ejecutable ...
  • Solíamos preocuparnos por la cantidad de memoria que consumían nuestros programas ...
  • Regularmente tomamos decisiones algorítmicas de intercambio de tiempo vs. espacio ...
  • Incluso en el back-end, que teníamos para escribir programas CGI en C o C ++ para nada para manejar un no decente. de RPS ... Fue varias órdenes de magnitud más rápido.
  • ¡Solíamos ejecutar pruebas sobre los méritos del rendimiento entre delphi / c ++ / vb!

Muy pocas personas tienen que preocuparse por estas cosas hoy.

Sin embargo, hace 10 años aún tenía que preocuparse de que su software se descargara a través de un módem de 56kb y se ejecutara en una PC de 5 años ... ¿Recuerda cuán malas eran las PC en 1996? Piense en términos de 4GB de disco duro, un procesador de 200Mhz y 128Mb de RAM ...

¿Y los servidores de hace 10 años? El servidor de "próxima generación" de Dell costó $ 2000 y vino con 2 (!) Procesadores pentium de 1Ghz, 2Gb o Ram, y un disco duro de 20Gb.

Era simplemente un juego de pelota diferente , y todos esos ingenieros "superiores" que tienen 10 años de experiencia (los tipos probablemente responderán a sus preguntas), se cortaron los dientes en ese entorno.


1
Los 20 años adicionales de experiencia también significan que tenemos las marcas de quemaduras de haber pasado por el proceso de optimización muchas, muchas veces y evitar hacer cosas que puedan necesitarlo más adelante. La misma razón por la que no me golpeo el pulgar (mucho) mientras uso un martillo.
Blrfl

1
bucle desenrollado <temblor>
rojo-tierra

55
y hoy todos los niños que pensaban que el ancho de banda, la CPU y la memoria eran ilimitados están descubriendo que sus aplicaciones móviles no funcionan muy bien.
gbjbaanb

9

ya hay 10 respuestas aquí y algunas son realmente buenas, pero debido a que esta es una molestia personal mía ...

La optimización prematura que a) lleva mucho más tiempo que una solución simple b) introduce más código donde la solución simple hubiera sido la mitad del tamaño y la mitad de la complejidad yc) hace que las cosas sean menos legibles. ABSOLUTAMENTE debe evitarse. Sin embargo, si un desarrollador tiene la opción de usar un std :: map o std :: vector y elige la colección incorrecta por pura ignorancia del rendimiento que es tan malo, si no peor, que la optimización prematura. ¿Qué pasaría si pudiera cambiar ligeramente su código hoy, mantener la legibilidad, mantener la misma complejidad, pero hacerlo más eficiente, lo haría? ¿O lo llamarías "optimización prematura"? Me parece que mucha gente ni siquiera pensaría en eso de una forma u otra.

Una vez que fui el tipo que aconsejó la "microoptimización" que requería muy poco cambio y recibí la misma respuesta que usted acaba de decir, "no debe optimizar demasiado pronto. Hagamos que funcione y lo cambiaremos". más tarde si hay un problema de rendimiento ". Tomó varios lanzamientos antes de arreglarlo. Y sí, fue un problema de rendimiento.

Si bien la optimización temprana puede no ser buena, creo que es muy beneficioso si las personas escriben código con la comprensión de lo que va a hacer ese código y no simplemente ignoran cualquier pregunta que resulte en la notación O (x) como "optimización". Ahora puede escribir un montón de código y, con un poco de reflexión sobre el rendimiento, evite el 80% de los problemas en el futuro.

También tenga en cuenta que muchos problemas de rendimiento no van a suceder en su entorno ni de inmediato. Algunas veces tendrá un cliente que supera el límite u otro desarrollador decide construir sobre su marco y aumentar el número de objetos 10 veces. Con algunas ideas sobre el rendimiento ahora, podría evitar un rediseño muy costoso más adelante. Y si el problema se encuentra después del lanzamiento oficial del software, incluso una solución simple se vuelve 20 veces más costosa de aplicar.

En conclusión, tener en cuenta el rendimiento en todo momento ayuda a desarrollar buenos hábitos. Que son tan importantes como tener un código limpio, lo más simple posible y organizado.


+1: este es uno de los factores de empleabilidad para aquellos contratados para desarrollar software Shrinkwrap y sitios web comerciales . La prevención es menos costosa que la maldición del cliente.
rwong

6

Sospecho que mucho de lo que está viendo es un simple error de muestreo. Cuando las personas se enfrentan a situaciones sencillas, escriben código y ese es el final de las cosas. Hacen preguntas cuando se trata de algo relativamente complicado, como la necesidad de optimizar, especialmente en una situación en la que no es necesariamente obvio que sería necesaria la optimización.

Dicho esto, indudablemente también hay una optimización prematura involucrada. Correctamente o de otro modo, C y C ++ tienen una reputación de rendimiento, lo que tiende a atraer a las personas que se preocupan por el rendimiento, incluidos aquellos que pueden hacer la optimización tanto por placer como porque realmente es necesario.


1
+1 - Nota: la mayoría de las preguntas SO con la etiqueta "rendimiento" probablemente sean parte de ese error de muestreo: P
Billy ONeal

3
Seguramente veo muchas preguntas de optimización prematura aquí ... Creo que proviene del hecho de que muchos programadores aficionados comienzan con la idea de escribir juegos, y hay un enorme corpus de libros de "optimización" sin sentido y sitios web relacionados con el desarrollo de juegos que ponen malas ideas en la cabeza de los principiantes. :-)
R ..

44
Cuando se trata de algo complicado, a menudo parece más fácil tomarse un descanso del problema complicado y desperdiciar su tiempo preocupándose por si debe usar i++o no++i
Carson63000

@ Carson63000: sí, eso podría sesgar totalmente las muestras. O pasan el tiempo respondiendo preguntas sobre por qué mi operator ++no compiló.
rwong

4

Algunas de las otras respuestas mencionan sistemas embebidos , y me gustaría ampliar esto.

Hay muchos dispositivos que contienen procesadores de gama baja, por ejemplo: el controlador de la caldera en su casa, o una simple calculadora de bolsillo, o las docenas de chips dentro de un automóvil moderno.

Para ahorrar dinero, estos pueden tener cantidades de flash (para almacenar código) y RAM que parecen pequeñas para aquellos que solo han escrito código para PC o teléfonos inteligentes. Para ahorrar energía, pueden funcionar a velocidades de reloj relativamente bajas.

Por poner un ejemplo, la familia de microcontroladores STM32 va desde 24 MHz, 16 KB flash y 4 KB de RAM , hasta 120 MHz, 1 MB flash y 128 KB RAM .

Al escribir código para chips como estos, se ahorra mucho tiempo si su objetivo es hacer que su código sea lo más eficiente posible de forma habitual. Obviamente, la optimización prematura sigue siendo una mala idea; pero con la práctica, aprende cómo se pueden resolver los problemas comunes rápidamente y / o con recursos mínimos, y codifica en consecuencia.


1
buenos puntos a tener en cuenta para sistemas integrados, un campo en el que trabajo yo mismo. Incluso con eso en mente, mi experiencia a lo largo de los años es que la optimización equivocada siempre es una pérdida de tiempo. Sin herramientas para guiarnos raramente encontramos las áreas problemáticas
Jeff

2

Estos son esencialmente lenguajes de bajo nivel, cuando uno se encuentra con un caso de rendimiento patológico en el que un detalle que no importaría el 99% del tiempo está causando el cuello de botella, uno realmente tiene la oportunidad de solucionar el problema directamente (a diferencia de la mayoría de los otros idiomas); pero, por supuesto, a menudo, cómo hacerlo de manera más efectiva no es evidente de inmediato. De ahí la mitad de las preguntas de micro-optimización extrañas / interesantes que se hacen aquí.

La otra mitad proviene de aquellos curiosos sobre lo cerca que pueden llegar al metal. Estos son esencialmente lenguajes de bajo nivel, después de todo ...


+1: vale la pena señalar que el "rendimiento patológico" podría sucederle a cualquier persona en el mundo, independientemente del idioma o la plataforma. La capacidad de volver a implementar en un lenguaje de nivel inferior para probar y leer el desmontaje puede proporcionar más información , pero no siempre proporciona una solución viable. Ejemplo: "Sé que puedo hacerlo en conjunto, ¡pero debe ejecutarse en un entorno de confianza parcial!"
rwong

2

El rendimiento siempre es un tema candente cuando se trata de C y C ++. Con respecto a qué tan lejos se debe llegar, siempre puede volverse loco hasta el punto de alinear ASM o usar la aritmética de puntero para una iteración más rápida. Sin embargo, llega un punto en el que uno pasa tanto tiempo optimizando que trabajar en el desarrollo del programa general se detiene.

Cuando se trata con estos problemas, hay rendimiento del programador y rendimiento del código. En cuál de estos enfocarse siempre surgirán preguntas interesantes. Al final, la pregunta más importante es qué tan notable es para el usuario. ¿Trabajará el usuario con datos que creen matrices con cientos o miles de elementos? En este caso, la codificación para hacer las cosas rápidamente puede hacer que su usuario se queje de que las operaciones estándar del programa son lentas.

Luego está el usuario que trabajará con pequeñas cantidades de datos. Algunos archivos aquí y allá, donde hacer cosas como ordenar y operaciones de archivo no será tan notable para el usuario si está utilizando funciones de nivel superior que le facilitan el mantenimiento a costa de algún rendimiento.

Este es solo un pequeño ejemplo de los problemas con los que se encontrará. Otros asuntos incluyen el hardware del usuario objetivo. Tendrás que preocuparte mucho más por el rendimiento si trabajas con sistemas embebidos, luego si tus usuarios tienen, por ejemplo, máquinas de doble núcleo con gigas de ram.


Hmm ... No uso la aritmética de puntero para una iteración más rápida: es una instrucción de multiplicar y agregar por bucle, ya sea que esté utilizando una iteración basada en índice o en puntero. Sin embargo, lo uso, porque generalmente es más claro que la iteración basada en índices.
Billy ONeal

La aritmética del puntero no es más rápida que w / e.

2

¿Por qué los programadores se preocupan tanto? Hay ideas tontas que pueblan sus cabezas, como resolver problemas de rendimiento antes de que se den cuenta de que los tienen, y no entender cuándo están adivinando .

Es complicado porque, en mi experiencia, hay algunos problemas de rendimiento en los que uno debería pensar con anticipación. Se necesita experiencia para saber cuáles son.

Dicho esto, el método que uso es similar al tuyo, pero no el mismo:

  1. Comience con el diseño más simple posible. En particular, la estructura de datos debe ser lo más normalizada y mínima posible. En la medida en que tenga una redundancia inevitable, uno debe ser tímido de las notificaciones como una forma de mantenerlo consistente. Es mejor tolerar la inconsistencia temporal y repararla con un proceso periódico.

  2. Cuando el programa esté en desarrollo, realice ajustes de rendimiento periódicamente, porque los problemas de rendimiento tienen una forma silenciosa de avanzar. El método que uso es una pausa aleatoria , porque creo que es el mejor.

Aquí hay un ejemplo paso a paso de lo que quiero decir.


1

Para ser honesto, depende de cuál sea su objetivo y si está programando profesionalmente o como un pasatiempo.

Hoy en día, las computadoras modernas son máquinas realmente poderosas. Independientemente de las operaciones básicas que decida hacer, ya sea que intente optimizar micro o no, pueden hacer que su trabajo sea notablemente rápido. Pero, por supuesto, si está haciendo algo más (por ejemplo, supercomputación para campos como la física o la química), es posible que desee optimizar tanto como desee.

Los primeros programadores del MIT no nacieron para hacer cosas increíbles; Comenzaron a simplificar y potenciar los algoritmos existentes. Su orgullo era hacer que 2 + 2 dieran cuatro en dos segundos menos que el algoritmo existente (eso es solo un ejemplo, se entiende la idea). Intentaron constantemente usar menos tarjetas perforadas en sus máquinas TI-83 para el rendimiento.

Además, si está programando para sistemas embebidos, entonces ciertamente debe vigilar el rendimiento micro. No desea tener un reloj digital lento que marque un segundo 5 nanosegundos antes que otro reloj digital.

Finalmente, si usted es un programador aficionado, entonces no hay ningún daño en optimizar los detalles más pequeños a pesar de que su programa es rápido. No es necesario, pero ciertamente es algo en lo que puede trabajar y aprovechar la oportunidad para aprender más. Si está trabajando profesionalmente en una pieza de software, no puede tomar ese lujo a menos que sea extremadamente necesario.


1
No creo que ser un programador aficionado tenga algo que ver con esto. El hecho de que no esté haciendo algo profesionalmente no significa necesariamente que tenga todo el tiempo del mundo para dedicarlo. Además, la mayoría de los aficionados van a cometer errores mayores, como elegir los algoritmos incorrectos, que la mayoría de los verdaderos profesionales cometerán. Además, el profesional probablemente está trabajando en un producto que procesa significativamente más datos que el aficionado (que por lo tanto debe ser más rápido), y tiene que mantener a los clientes contentos con el rendimiento de la aplicación. Los aficionados no tienen tales limitaciones.
Billy ONeal

No lo hacen, pero ciertamente tienen más tiempo para trabajar en ellos simplemente si lo desean.

3
Yo diría lo contrario. Tengo 8 horas o más al día para trabajar en algo como profesional. Tengo 1, tal vez 2 horas por día para mis proyectos de hobby.
Billy ONeal

1

usando un algoritmo O (N2) vs O (NlogN) en una lista de 100 elementos.

Estuve en una situación similar recientemente. Tenía una gran variedad de artículos. En el caso esperado, había dos (!) Elementos en la lista, e incluso en el peor de los casos, no espero más de cuatro u ocho.

Necesitaba ordenar esa lista. Resulta que reemplazar std::sortcon una red de clasificación (esencialmente muchos ifs anidados ) redujo un gran porcentaje del tiempo de ejecución (no recuerdo el número, pero fue algo así como 10-20%). Este es un gran beneficio de una microoptimización, y el código es absolutamente crítico para el rendimiento.

Por supuesto, solo hice esto después de perfilar. Pero el punto es que si uso un lenguaje que es tan inconveniente y complicado como C ++ (sin mencionar sus reglas exasperantemente complejas para la resolución de sobrecarga), entonces quiero obtener todos los beneficios.


1

Uso acumulativo de energía

Hay una respuesta que siempre creo que falta en estas discusiones y que me molesta un poco: el uso acumulativo de energía .

Claro, tal vez no importe mucho si escribe su programa en un lenguaje interpretado de alto nivel, y lo deja correr en un navegador con un par de capas de indirección, o si su ciclo toma 0.01 segundos en lugar de 0.001 segundos. Nadie lo notará, es decir, ningún usuario individual lo notará.

Pero cuando decenas de miles, o incluso millones de usuarios en algunos casos usan su código, se suma toda esa ineficiencia adicional. Si su herramienta evita que una CPU entre en el estado de suspensión durante solo diez segundos por día, y un millón de usuarios la usen, su algoritmo ineficiente solo consumió 140 kWh [1] adicionales por día.

Raramente veo esto discutido, y creo que es triste. Sospecho firmemente que las cifras son mucho peores para los marcos populares, como Firefox, y aplicaciones web interactivas elegantes, y sería interesante investigar.


[1] Lo acabo de inventar, 10 millones de segundos por 50 vatios. La cifra exacta depende de muchas cosas.


1
Debería comenzar por mencionar la palabra mágica "móvil". Cuando se ejecuta en una plataforma de escritorio, una aplicación que requiere 1/100 segundos de tiempo de CPU para dibujar un marco 60 veces por segundo será "lo suficientemente rápido"; mejorar el rendimiento diez veces no supondría ninguna diferencia para el usuario. Sin embargo, en una plataforma móvil, una aplicación que se ejecuta al 90% de la CPU puede consumir baterías mucho más rápido que al 10%.
supercat

1

A veces solo tiene algoritmos que no pueden ser mejores que el tiempo lineal para el que todavía hay una fuerte demanda de rendimiento.

Un ejemplo es el procesamiento de video en el que no puede hacer que una imagen / marco sea más brillante como un ejemplo básico sin recorrer cada píxel (bueno, supongo que puede hacerlo con algún tipo de estructura jerárquica que indique las propiedades heredadas por los niños que finalmente descienden en mosaicos de imágenes para los nodos de hoja, pero luego diferiría un costo mayor de recorrer cada píxel hasta el renderizador y el código probablemente sería más difícil de mantener que incluso el filtro de imagen más micro optimizado).

Hay muchos casos como ese en mi campo. Tiendo a estar haciendo más bucles de complejidad lineal que tienen que tocar todo o leer todo que los que se benefician de cualquier tipo de estructura de datos sofisticada o algoritmo. No hay trabajo que se pueda omitir cuando hay que tocar todo. Entonces, en ese punto, si inevitablemente se trata de una complejidad lineal, debe hacer el trabajo por iteración cada vez más barato.

Entonces, en mi caso, las optimizaciones más importantes y comunes son a menudo representaciones de datos y diseños de memoria, subprocesos múltiples y SIMD (generalmente en este orden, siendo la representación de datos la más importante, ya que afecta la capacidad de hacer las dos últimas). No me encuentro con tantos problemas que se resuelven con árboles, tablas hash, algoritmos de clasificación y cosas por el estilo. Mi código diario está más en la línea de "para cada cosa, hacer algo".

Por supuesto, es otro caso para hablar cuando las optimizaciones son necesarias (y más importante, cuando no lo son), micro o algorítmicas. Pero en mi caso particular, si una ruta de ejecución crítica necesita optimización, las ganancias de velocidad de 10x + a menudo se logran mediante optimizaciones de micro nivel como subprocesos múltiples, SIMD y reorganizando diseños de memoria y patrones de acceso para mejorar la localidad de referencia. No es tan frecuente que, digamos, reemplace un tipo de burbuja con un introsort o una clasificación de radix o detección de colisión de complejidad cuadrática con un BVH tanto como encontrar puntos de acceso que, por ejemplo, se benefician de la división de campo caliente / frío.

Ahora, en mi caso, mi campo es tan crítico para el rendimiento (trazado de rayos, motores de física, etc.) que un rastreador lento pero perfectamente correcto que tarda 10 horas en representar una imagen a menudo se considera inútil o más que uno rápido que es completamente interactivo pero emite las imágenes más feas con rayos que se escapan por todas partes debido a la falta de rayos impermeables / tri intersección. Podría decirse que la velocidad es la métrica de calidad principal de dicho software, posiblemente incluso más que la corrección hasta cierto punto (ya que la "corrección" es una idea confusa con el trazado de rayos ya que todo es aproximado, siempre que no se bloquee ni nada de eso). Y cuando ese es el caso, si no pienso en la eficiencia por adelantado, encuentro que tengo que cambiar el código al nivel de diseño más costoso para manejar diseños más eficientes. Entonces si no lo hago

El juego es otro campo similar al mío. No importa cuán correcta sea la lógica de su juego o cuán sostenible y ingeniosamente diseñada sea su base de código si su juego se ejecuta a 1 fotograma por segundo como una presentación de diapositivas. En ciertos campos, la falta de velocidad podría hacer que la aplicación sea inútil para sus usuarios. A diferencia de los juegos, no existe una métrica "suficientemente buena" en áreas como el trazado de rayos. Los usuarios siempre quieren más velocidad, y la competencia industrial es predominantemente en la búsqueda de soluciones más rápidas. Nunca será lo suficientemente bueno hasta que sea en tiempo real, momento en el cual los juegos usarán trazadores de ruta. Y entonces probablemente todavía no sea lo suficientemente bueno para VFX, ya que entonces los artistas podrían querer cargar miles de millones de polígonos y tener simulaciones de partículas con auto-colisión entre miles de millones de partículas a más de 30 FPS.

Ahora, si te sirve de consuelo, a pesar de eso, todavía escribo alrededor del 90% del código en un lenguaje de script (Lua) sin ninguna preocupación por el rendimiento. Pero tengo una cantidad de código inusualmente grande que realmente necesita pasar de millones a miles de millones de cosas, y cuando estás pasando de millones a miles de millones de cosas, comienzas a notar una diferencia épica entre el ingenuo código de un solo hilo que invoca un error de caché con cada iteración frente a, digamos, código vectorizado que se ejecuta en paralelo y accede a bloques contiguos donde no se cargan datos irrelevantes en una línea de caché.


0

Como mencionó, no tiene importancia preocuparse por los problemas de micro rendimiento antes de tener en cuenta algunos problemas realmente causados ​​por estos problemas


0

Es realmente imposible responder esta pregunta en general. La mayoría del software que se está creando hoy en día son sitios web internos y aplicaciones LOB, y para ese tipo de programación su razonamiento es bastante correcto. Por otro lado, si está escribiendo algo como un controlador de dispositivo o un motor de juego, ninguna optimización es "prematura"; Lo más probable es que su software se ejecute en sistemas muy diferentes con diferentes restricciones de hardware. En ese caso, debe diseñar para el rendimiento y asegurarse de no elegir un algoritmo subóptimo.


Exactamente lo que quería decir. Cada pieza de software tiene su dominio de aplicación y no debe esperarse que se comporte de manera óptima fuera de él. En este sentido, la optimización prematura es un ejemplo de perfeccionismo equivocado.
K.Steff

0

Creo que el problema del programador, que se preocupa tanto por el rendimiento, es que a veces en su vida, necesitaba escribir código de microperformantes, tal vez con mucha urgencia, y aprendió, aprendió, aprendió y al final supo un Muchas cosas y trucos.

Y ahora es difícil de olvidar, y sin una medición previa, lo que demuestra que no necesita preocuparse, está en el lado seguro, usando un código rápido.

Siempre es bueno mostrar su profundo conocimiento, sus habilidades y algunos trucos, y reutilizar algo que ha aprendido. Te hace sentir valioso y el tiempo que pasas aprendiendo, valiendo la pena.

A veces en mi vida, aprendí que el incremento de prefijos es más rápido ...

for (int i = 0; i < MAX; ++i)

... que el incremento de postfix:

for (int i = 0; i < MAX; i++)

Ahora, si MAX es bajo, no importará, y si hay trabajo real en el ciclo, tampoco importará. Pero no hay una razón para usar la versión postfix, incluso si el compilador de hoy optimiza el código por su cuenta.

Tal vez los buscadores de rendimiento necesitan un objetivo adicional, además de escribir 'código de trabajo', como 'código de trabajo y legible' para tener una guía en el gran mar de opciones.


0

¿He tenido la suerte de no tener que preocuparme demasiado por ello, o soy un mal programador?

¿Te importan tus requisitos? Si el rendimiento no es un requisito, no te preocupes por eso. Pasar un tiempo significativo en él es perjudicial para su empleador.

Hasta cierto punto, el rendimiento es siempre un requisito. Si puede golpearlo sin pensarlo, está justificado no pensar en ello.

Personalmente, la mayoría de las veces me impulsa el rendimiento cuando mis pruebas tardan mucho en pasar. Estoy demasiado impaciente como para esperar 5 minutos mientras pasan un conjunto de pruebas. Pero eso generalmente se resuelve jugando con las pruebas.

Mi pregunta es ¿por qué una gran cantidad de programadores se preocupan tanto? ¿Es realmente un problema para la mayoría de los desarrolladores,

Hay un gran número de programadores que están justificados en cuánto les importa. Hay grandes números que no lo son. Hablemos de los que no lo son.

Una de las primeras cosas que los programadores aprenden en la escuela, después de cómo hacer que las cosas funcionen realmente, es la gran notación O. Muchos de ellos aprenden la lección correctamente y, por lo tanto, se enfocan adecuadamente en cosas impactadas dramáticamente por n. Otros no entienden las matemáticas y solo le quitan la lección de que una vez que funciona debe ser rápido. Peor aún, algunos de estos estudiantes nunca aprenden nada más sobre lo que es importante hacer con su código, además de hacerlo funcionar y hacerlo funcionar rápidamente. Las lecciones perdidas: hazlo legible, diseña bien, no juegues sin razón alguna.

Knuth tenía razón: la optimización prematura es la raíz de todo mal. Pero una vez que funciona, ¿cuál es el siguiente paso? Rápido derecho? ¡NO! El siguiente paso es legible. Legible es el primer, próximo, medio y último paso. Muchas de las personas que encuentro haciendo optimizaciones de rendimiento innecesarias arrojan legibilidad debajo del bus.

Algunos incluso obtienen una emoción perversa por lo ilegible que es su código. Han tenido que sufrir al mirar el código difícil de entender creado por otros, así que ahora es su turno de recuperación.

Sé esto porque solía hacer esto. Una vez refactoricé una línea 5 perfectamente legible si la estructura se reducía a una expresión booleana de una línea indescifrable y la envié orgullosamente a mi profesor esperando impresionar ya que podía crear algo tan compacto e intimidante. No recibí los elogios que esperaba.

Si el código sigue siendo legible, hacerlo rápido más tarde es fácil. Es por eso que Knuth enfatiza "prematuro" no "innecesario". Porque seguro, más rápido es mejor. Pero mejor es mejor dependiendo de lo que sacrifiques por él. Así que espere hasta saber qué rendimiento necesita realmente antes de hacer sacrificios por él. Sacrifique la legibilidad de mala gana porque una vez que se ha ido, es difícil volver.

Más allá de la legibilidad está todo el mundo del diseño de software. De qué trata este sitio. Algunos no tienen idea de qué hacer en cuanto al diseño. Entonces, como no pueden impresionar con el diseño, hacen un desastre indescifrable para que las personas no puedan decir que no tienen idea. Dado que nadie corrige su código, debe ser un buen código, ¿verdad?

Para algunos, el rendimiento es la excusa para hacer lo que quieran. Los programadores tienen mucho poder y autonomía. Se ha depositado confianza en ellos. No abuses de la confianza.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.