¿La programación modular afecta el tiempo de cálculo?

19

Todos dicen que debería hacer que mi código sea modular, pero ¿no es menos eficiente si uso más llamadas a métodos en lugar de menos métodos, pero más grandes? ¿Cuál es la diferencia en Java, C o C ++ para el caso?

Entiendo que es más fácil editar, leer y comprender, especialmente en un grupo. Entonces, ¿la pérdida de tiempo de cálculo es insignificante en comparación con los beneficios de ordenamiento del código?

fatsokol
fuente
2
La pregunta es cuánto tiempo llevará el tiempo de procesamiento que ahorre pasando el tiempo dedicado al mantenimiento más difícil. La respuesta a eso depende completamente de su aplicación.
Blrfl
2
Muchas buenas preguntas generan cierto grado de opinión basado en la experiencia de expertos, pero las respuestas a esta pregunta tenderán a basarse casi por completo en opiniones, en lugar de hechos, referencias o experiencia específica.
mosquito
10
También vale la pena señalar que la penalización de cálculo para una llamada a función o método es tan minúscula que incluso en un programa muy grande con muchas funciones y llamadas a métodos, hacer esas llamadas ni siquiera se clasifica en el gráfico.
greyfade
1
@greyfade: Eso es cierto para los saltos directos, pero el salto indirecto adicional predicho podría costar, por ejemplo, ~ 3% del tiempo total de ejecución del programa (solo un número del programa que he verificado recientemente, aunque podría no haber sido representativo). Dependiendo de su área, podría o no considerarlo significativo, pero se registró en el gráfico (y, por supuesto, es al menos parcialmente ortogonal a la modularidad).
Maciej Piechotka
44
La optimización prematura es la fuente de todos los males. El código lineal es un poco más rápido que el código modular. El código modular es mucho más rápido que el código de espagueti. Si apuntas al código lineal sin un proyecto muy (MUY) exhaustivo de todo, terminarás con un código de espagueti, te lo garantizo.
SF.

Respuestas:

46

Sí, es irrelevante.

Las computadoras son incansables, motores de ejecución casi perfectos que trabajan a velocidades totalmente incomparables a los cerebros. Si bien hay una cantidad de tiempo medible que una llamada de función agrega al tiempo de ejecución de un programa, esto no es nada en comparación con el tiempo adicional que necesita el cerebro de la siguiente persona involucrada con el código cuando tienen que desenredar la rutina ilegible incluso comenzar a entender cómo trabajar con él. Puede probar el cálculo como una broma: suponga que su código debe mantenerse solo una vez , y solo agrega media hora al tiempo necesario para que alguien acepte el código. Tome la velocidad de reloj de su procesador y calcule: ¿cuántas veces tendría que ejecutarse el código para soñar con compensar eso?

En resumen, sentir lástima por la CPU es completamente, totalmente equivocado el 99.99% del tiempo. Para los raros casos restantes, use perfiladores. No , no asumir que se puede detectar esos casos - no se puede.

Kilian Foth
fuente
2
Si bien estoy de acuerdo en que la mayoría de las veces es una optimización prematura, creo que su discusión con el tiempo es mala. El tiempo es relativo en diferentes situaciones y no puedes simplemente calcular como lo hiciste.
Honza Brabec
28
+1 solo por la expresión "compadecerse de la CPU", porque acentúa muy bien el malentendido en la optimización prematura.
Michael Borgwardt
44
Altamente relacionado: ¿Qué tan rápido son las computadoras en términos cotidianos? Salir de su camino para evitar algunas llamadas a la función de "velocidad" es como salir de su camino para salvar a alguien un minuto en total a lo largo de toda su vida . En resumen: ni siquiera vale la pena el tiempo que toma considerar.
BlueRaja - Danny Pflughoeft
77
+1 por vender tan elocuentemente lo que falta, pero se olvidó de agregar el peso más importante a la balanza que hace que compadecer la CPU sea aún más grave: ignorar el dinero perdido y el tiempo dedicado a ese mantenimiento único; Si tomó 1 segundo, todavía hay un sumidero mucho más insidioso allí. Riesgo de error . Cuanto más confuso y difícil sea para el responsable de mantenimiento posterior cambiar su código, mayor será el riesgo de que implemente errores en él, lo que puede causar costos inevitables e ineludibles por el riesgo para los usuarios y cualquier error ocasiona el mantenimiento posterior (lo que puede causar errores ...)
Jimmy Hoffa
Un viejo maestro mío solía decir: "Si estás pensando si estás optimizando prematuramente o no, detente aquí. Si esta fue la elección correcta, lo sabrías".
Ven
22

Depende.

En el mundo glacialmente lento que es la programación web, donde todo sucede a velocidades humanas, la programación de método pesado, donde el costo de la llamada al método es comparable o excede el costo del procesamiento realizado por el método, probablemente no importa .

En el mundo de la programación de sistemas embebidos y los manejadores de interrupciones para interrupciones de alta velocidad, ciertamente importa. En ese entorno, los modelos habituales de "acceso a la memoria es barato" y "el procesador es infinitamente rápido" se descomponen. He visto lo que sucede cuando un programador orientado a objetos de mainframe escribe su primer controlador de interrupciones de alta velocidad. No fue lindo.

Hace varios años, estaba coloreando blobs de conectividad de 8 vías no recursivos en imágenes FLIR en tiempo real, en lo que en ese momento era un procesador decente. El primer intento utilizó una llamada de subrutina, y la sobrecarga de la llamada de subrutina se comió el procesador vivo. (4 llamadas POR PÍXEL x 64K píxeles por cuadro x 30 cuadros por segundo = lo resuelves). El segundo intento cambió la subrutina a una macro C, sin pérdida de legibilidad, y todo fue rosas.

Tienes que mirar DURO a lo que estás haciendo y al entorno en el que lo harás.

John R. Strohm
fuente
Recuerde que la mayoría de los problemas de programación modernos se solucionan mejor con muchos códigos orientados a objetos y basados ​​en métodos. Lo sabrás cuando estés en un entorno de sistemas integrados.
Kevin - Restablece a Mónica el
44
+1, pero con una advertencia: generalmente es el caso de que un compilador de optimización a menudo hará un mejor trabajo de alineación, desenrollado de bucles y optimizaciones escalares y vectoriales que una persona. El programador todavía necesita conocer muy bien el lenguaje, el compilador y la máquina para aprovechar esto, por lo que no es mágico.
detly
2
Eso es cierto, pero optimizó su código después de escribir la lógica y perfilarlo para encontrar la parte problemática en su algoritmo. No comenzaste a codificar por rendimiento sino por legibilidad. Y las macros lo ayudarán a mantener su código legible.
Uwe Plonus
2
Incluso en la programación de sistemas embebidos, comience escribiendo un código correcto claro y solo comience a optimizar después de eso si es necesario y según lo guíe la creación de perfiles . De lo contrario, es demasiado fácil poner mucho trabajo que no tiene un efecto real en el rendimiento / tamaño del código y que hace que la fuente sea difícil de entender.
Donal Fellows
1
@detly: Sí y no. Los compiladores modernos pueden EN GENERAL hacer un mejor trabajo DENTRO DE LOS LÍMITES IMPUESTOS POR EL IDIOMA. El programador humano sabe si los límites impuestos por el lenguaje son aplicables en el caso particular en cuestión. Por ejemplo, los estándares de lenguaje requieren compiladores de Cn y C ++ para permitir que el programador haga ciertas cosas muy patológicas y genere código que funcione de todos modos. El programador puede saber que no está loco, ni ser tan estúpido o aventurero como para hacer esas cosas, y hacer optimizaciones que de otro modo serían inseguras, porque sabe que son seguras en este caso.
John R. Strohm
11

En primer lugar: los programas en un idioma superior son para ser leídos por humanos y no por máquinas.

Así que escribir los programas para que usted los entiende. No piense en el rendimiento (si tiene problemas graves de rendimiento, perfile su aplicación y mejore el rendimiento donde sea necesario).

Incluso si es cierto que llamar a un método o función requiere cierta sobrecarga, esto no importa. Hoy los compiladores deberían poder compilar su código en un lenguaje de máquina eficiente para que el código generado sea eficiente para la arquitectura de destino. Use los interruptores de optimización de su compilador para obtener el código eficiente.

Uwe Plonus
fuente
55
Yo diría que deberíamos escribir programas de tal manera que otros los entiendan.
Bartlomiej Lewandowski
La primera persona que tiene que leer un programa es el propio desarrollador. Esa es la razón por la que escribí que . Si otras personas pueden leer el programa, también es agradable pero (a mis ojos) no es necesario (en primer lugar). Si trabaja en un equipo, otras personas también deberían entender el programa, pero mi intención era que esa persona tuviera que leer un programa, no computadoras.
Uwe Plonus
5

Por lo general, cuando de lo contrario tendría una función grande y la dividiera en muchas más pequeñas, estas se alinearán porque la única desventaja de la inserción (repetir las mismas instrucciones demasiado) no es relevante en este caso. Eso significa que su código actuará como si hubiera escrito una función grande.

Si no están alineados por algún motivo y esto se convierte en un problema de rendimiento, entonces debe considerar la inserción manual. No todas las aplicaciones son formularios CRUD en red con enormes latencias intrínsecas.

Esailija
fuente
2

Probablemente no hay costo de cálculo. Por lo general, los compiladores / JIT de los últimos 10-20 años más o menos se ocupan de la función de alineación perfectamente bien. Para C / C ++ generalmente se limita a funciones 'inlinable' (es decir, la definición de función está disponible para el compilador durante la compilación, es decir, está en el encabezado del mismo archivo), pero las técnicas actuales de LTO lo superan.

Si debe dedicar tiempo a la optimización depende del área en la que esté trabajando. Si maneja una aplicación 'normal' que pasó la mayor parte del tiempo esperando la entrada, probablemente no debería preocuparse por las optimizaciones a menos que la aplicación se sienta 'lenta'.

Incluso en tales casos, debe concentrarse en muchas cosas antes de hacer la microoptimización:

  • ¿Dónde están los problemas? Por lo general, las personas tienen dificultades para encontrar los puntos de acceso, ya que leemos el código fuente de manera diferente. Tenemos diferentes proporciones de tiempo de operación y las realizamos secuencialmente mientras que los procesadores modernos no .
  • ¿Se necesita algún cálculo cada vez? Por ejemplo, si cambia un solo parámetro de miles, es posible que desee calcular solo una parte afectada en lugar del modelo completo.
  • ¿Usas algoritmo óptimo? Cambiar de O(n)a O(log n)podría tener un impacto mucho mayor que cualquier cosa que pueda lograr con la microoptimización.
  • ¿Usas estructuras adecuadas? Supongamos que está utilizando un Listcuando lo necesita HashSetpara tener O(n)búsquedas cuando podría haberlo hecho O(1).
  • ¿Usas paralelismo eficientemente? Actualmente, incluso los teléfonos móviles pueden tener 4 núcleos o más, podría ser tentador usar hilos. Sin embargo, no son una bala de plata, ya que tienen un costo de sincronización (sin mencionar que si el problema está ligado a la memoria, no tienen sentido de todos modos).

Incluso si decide que necesita realizar una microoptimización (lo que prácticamente significa que su software se usa en HPC, incrustado o simplemente utilizado por un gran número de personas; de lo contrario, el costo adicional de mantenimiento supera los costos de tiempo de la computadora) que necesita para identificar los puntos de acceso (núcleos) que desea acelerar. Pero entonces probablemente deberías:

  1. Conozca exactamente la plataforma en la que está trabajando
  2. Conozca exactamente el compilador en el que está trabajando y qué optimización es capaz de hacer y cómo escribir código idiomático para permitir esa optimización
  3. Piense en los patrones de acceso a la memoria y cuánto puede caber en la memoria caché (tamaño exacto que conoce desde el punto 1).
  4. Entonces, si está obligado a calcular, piense en reorganizar el cálculo para guardar el cálculo

Como comentario final. Por lo general, el único problema que tiene con las llamadas a métodos son los saltos indirectos (métodos virtuales) que no fueron predichos por el predictor de rama (desafortunadamente, el salto indirecto es el caso difícil). Sin embargo:

  • Java tiene un JIT que en muchos casos puede predecir el tipo de clase y, por lo tanto, un objetivo de salto de antemano y, por lo tanto, en los puntos de acceso no debería tener muchos problemas.
  • Los compiladores de C ++ a menudo realizan un análisis de programa y al menos en algunos casos pueden predecir el objetivo en el momento de la compilación.
  • En ambos casos, cuando el objetivo ha sido predicho, la alineación debería funcionar. Si el compilador no pudo realizar las posibilidades de alineación, nosotros tampoco podríamos hacerlo.
Maciej Piechotka
fuente
0

Mi respuesta probablemente no se ampliará demasiado en las respuestas existentes, pero siento que mis dos centavos pueden ser útiles.

Antes que nada; sí, por modularidad, generalmente renuncia a cierto nivel de tiempo de ejecución. Escribir todo en el código de ensamblaje te dará la mejor velocidad. Dicho eso ...

¿Conoces YouTube? ¿Es probable que sea el sitio de mayor ancho de banda existente o el segundo después de Netflix? Escriben una gran parte de su código en Python, que es un lenguaje altamente modular que no está construido para un rendimiento de primer nivel.

El problema es que cuando algo sale mal y los usuarios se quejan de que los videos se cargan lentamente, no hay muchos escenarios en los que esa lentitud se atribuya a la lenta velocidad de ejecución de Python. Sin embargo, la rápida compilación de Python y su capacidad modular para probar cosas nuevas sin verificación de tipo probablemente permitirán a los ingenieros depurar lo que está sucediendo bastante rápido ("Wow. Nuestro nuevo interno escribió un bucle que hace una nueva subconsulta SQL para CADA resultado ") o (" Oh, Firefox ha dejado de usar ese antiguo formato de encabezado de almacenamiento en caché; e hicieron una biblioteca de Python para configurar el nuevo fácilmente ")

En ese sentido, incluso en términos de tiempo de ejecución, un lenguaje modular puede considerarse más rápido porque una vez que encuentre cuáles son sus cuellos de botella, probablemente será más fácil reorganizar su código para que funcione de la mejor manera. Muchos ingenieros le dirán que los grandes golpes de rendimiento no fueron donde pensaban que estarían (y, de hecho, las cosas que optimizaron apenas fueron necesarias; ¡o ni siquiera funcionaron de la manera que esperaban!)

Katana314
fuente
0

Si y no. Como otros han señalado el programa de legibilidad primero, luego de eficiencia. Sin embargo, hay prácticas estándar que son legibles y eficientes. La mayoría del código se ejecuta con poca frecuencia, y de todos modos no obtendrá muchas ventajas al optimizarlo.

Java puede alinear llamadas a funciones más pequeñas, por lo que hay pocas razones para evitar escribir las funciones. Los optimizadores tienden a funcionar mejor con un código más simple y fácil de leer. Hay estudios que muestran atajos que, en teoría, deberían ejecutarse más rápido, en realidad tomar más tiempo. Es probable que el compilador JIT funcione mejor, el código es más pequeño y las piezas ejecutadas con frecuencia se pueden identificar y optimizar. No lo he probado, pero esperaría que una función grande que se llama relativamente raramente no se compile.

Es probable que esto no se aplique a Java, pero un estudio encontró que las funciones más grandes en realidad se ejecutaron más lentamente debido a que requieren un modelo de referencia de memoria diferente. Esto fue específico de hardware y optimizador. Para módulos más pequeños se utilizaron instrucciones que funcionaban dentro de una página de memoria. Estos fueron más rápidos y pequeños que las instrucciones requeridas cuando la función no se ajustaba a una página.

Hay casos en los que vale la pena optimizar el código, pero en general es necesario perfilar el código para determinar dónde está. Creo que a menudo no es el código que esperaba.

BillThor
fuente