¿Cuándo es un buen momento para razonar sobre el desempeño en Haskell?

8

Simon Peyton Jones mismo reconoce que razonar sobre el desempeño en Haskell es difícil debido a la semántica no estricta.

Todavía tengo que escribir un proyecto importante en Haskell, así que me pregunto: ¿puedo razonar sobre el rendimiento solo al comienzo de un proyecto (al elegir estructuras de datos básicas y biblioteca de E / S) y cuando surja un problema, lidiar con el perfilador?

Para decirlo de otra manera, ¿es posible (es decir, no demasiado doloroso) posponer el manejo del rendimiento cuando tiene problemas de rendimiento, o tiene que aprender a predecir cómo GHC ejecutará su código (por ejemplo: infiera lo que decidirá el analizador de rigor) )?

Simon Bergot
fuente
Cuando el rendimiento importa.
Thomas Eding

Respuestas:

7

Las otras respuestas brindan amplios consejos sobre el razonamiento del desempeño. Esta respuesta aborda específicamente la semántica no estricta.

Si bien la pereza hace que sea más difícil razonar sobre el rendimiento, no es tan complicado como podría pensar. Aunque la pereza es bastante útil en algunas situaciones, la mayoría de las veces se usa un lenguaje perezoso de la misma manera que se usaría un lenguaje estricto. En consecuencia, el razonamiento de rendimiento para lenguajes estrictos se puede aplicar (con algunos ajustes) a lenguajes perezosos.

En términos de complejidad de tiempo, la evaluación entusiasta hace estrictamente más trabajo que la evaluación perezosa. Ambas estrategias producen el mismo resultado en la mayoría de los casos. (Más precisamente, si la evaluación entusiasta no se encuentra con ningún error, produce el mismo resultado que la evaluación diferida). Por lo tanto, para razonar sobre la complejidad temporal de un programa Haskell, puede pretender que evalúa con entusiasmo. En aquellas situaciones poco frecuentes donde la pereza es importante, esta estimación será demasiado alta y debería revisarse a la baja.

Si bien la evaluación diferida le brinda una menor complejidad de tiempo que una evaluación entusiasta, a veces le brinda una mayor complejidad espacial, es decir, fugas de espacio. Se puede corregir una mayor complejidad espacial agregando anotaciones de rigor para hacer que un programa se ejecute con más entusiasmo. Las herramientas de perfilado son bastante buenas para rastrear la causa de las fugas de espacio. Clasificaría esto como depuración de corrección o depuración de rendimiento, dependiendo de la gravedad.

Disipador de calor
fuente
In terms of time complexity, eager evaluation does strictly more work than lazy evaluation. ¿En qué basas esa afirmación? Suponiendo que la evaluación ansiosa terminará en el mismo punto que la evaluación perezosa (es decir, no entrar en una evaluación perezosa de secuencias infinitas y cosas así) un algoritmo de generación ansiosa siempre será tan rápido o más rápido (es decir, hacer menos trabajo) que un algoritmo perezoso porque no requiere la sobrecarga de llamadas repetidas en una rutina de generador.
Mason Wheeler
2
Creo que la razón por la que los amo, muchachos de Haskell, es porque apenas entiendo de qué están hablando.
Erik Reppen
3
@MasonWheeler: Lo más probable es que esté hablando de la complejidad asintótica . Los factores constantes son un problema de implementación. Además, incluso si ambos programas terminan, el no estricto podría hacer mucho menos trabajo: considere all even [1..1e10]tanto una versión estricta como una perezosa all. El compilador también tiene más margen para elegir el orden de evaluación en un lenguaje como Haskell con cosas como la fusión en bucle.
Tikhon Jelvis
1
La evaluación de @MasonWheeler Lazy evita evaluar cualquier término que no sea necesario. No pude encontrar una referencia autorizada, pero aquí hay una que indica que la evaluación perezosa realiza menos reducciones que la evaluación entusiasta: en.wikibooks.org/wiki/Haskell/…
Disipador de calor
1

Optimice las cosas grandes antes de codificar, y las pequeñas cosas cuando haya terminado.

Por ejemplo, antes de comenzar a codificar, debe pensar en lo siguiente:

  • ¿Son las bibliotecas / marcos que va a utilizar decentemente rápido?
  • Intenta mantener tus estructuras de datos simples.
  • Intente mantener sus algoritmos y patrones de diseño lo más simples posible.
  • ¿Qué tan rápido es el idioma que estoy usando?

...y así.

Luego, cuando casi haya terminado con su código, piense en las pequeñas cosas como qué función incorporada es más rápida, ¿debería reescribir esa área de código para hacerlo más eficiente, etc.

Esto es cierto para cualquier idioma, y ​​realmente depende del tipo de software que esté escribiendo. Haskell es un lenguaje de propósito general, por lo que probablemente (corríjame si me equivoco) no está haciendo nada que deba ser extremadamente rápido. Si es así, debe usar un lenguaje de nivel inferior. Si la velocidad es un problema, pero no lo suficiente como para usar un lenguaje de bajo nivel, entonces debe optimizar su rendimiento en base a lo anterior.

Por supuesto, todo el mundo hace las cosas de manera diferente, por lo que si su experiencia personal le hace pensar que debería hacerlo de manera diferente según su situación, entonces probablemente debería hacerlo.

Dinámica
fuente
55
El problema es que, en la mayoría de los idiomas, puedo razonar fácilmente sobre el comportamiento en tiempo de ejecución. En Haskell, esto es mucho más difícil. Por lo tanto, es más probable que tome un enfoque equivocado y aumente la probabilidad de errores de rendimiento.
Simon Bergot
La respuesta comienza bien, luego dices "mira las pequeñas cosas que se pueden mejorar" ... sí, no. Perfil.
Florian Margaine
3
Esta respuesta es demasiado amplia y no es muy útil para Haskell. Suponga que el OP ya sabe acerca de la optimización macro vs micro. ¿Cómo se aplica esta respuesta a Haskell , un lenguaje no estricto para el que es más difícil razonar sobre el tiempo de ejecución y el rendimiento de la memoria?
Andres F.
@FlorianMargaine Profile?
Dinámico
0

Me gustaría agregar a la respuesta de Dynamic:

  • Haga su código modular y acoplado libremente .
  • Ocultar detalles de implementación de sus módulos.

Cuando se dé cuenta más adelante en el desarrollo de los cuellos de botella que tiene su código, puede ser realmente doloroso refactorizar todo el proyecto. Si su código está bien estructurado, los cuellos de botella son más fáciles de encontrar y optimizar / corregir.

Petr Pudlák
fuente