2012-02-17 7 views
8

Estoy tratando de comprender una anomalía de rendimiento que se observa cuando se ejecuta un programa en runhaskell.Anomalía de rendimiento Runhaskell

El programa en cuestión es:

isFactor n = (0 ==) . (mod n) 
factors x = filter (isFactor x) [2..x] 
main = putStrLn $ show $ sum $ factors 10000000 

Cuando ejecuto esto, se tarda 1,18 segundos.

Sin embargo, si redefino isFactor como:

isFactor n f = (0 ==) (mod n f) 

continuación, el programa toma 17,7 segundos.

Esa es una gran diferencia en el rendimiento y espero que los programas sean equivalentes. ¿Alguien sabe lo que me falta aquí?

Nota: Esto no ocurre cuando se compila bajo GHC.

+1

Supongo que como runhaskell realiza solo unas pocas optimizaciones, la segunda sufre ciertos problemas de rigor. – fuz

Respuesta

9

Aunque las funciones deben ser idénticos, hay una diferencia en cómo se aplican . Con la primera definición, isFactor se aplica completamente en el sitio de llamada isFactor x. En la segunda definición, no lo es, porque ahora isFactor toma explícitamente dos argumentos.

Incluso optimizaciones mínimas son suficientes para que GHC vea esto y cree un código idéntico para ambas; sin embargo, si compila con -O0 -ddump-simpl puede determinar que, sin optimizaciones, esto hace una diferencia (al menos con ghc-7.2.1 , YMMV con otras versiones).

Con la primera isFactor, GHC crea una única función que se pasa como el predicado de "GHC.List.Filter", con las llamadas a mod 10000000 y (==) inline. Para la segunda definición, lo que ocurre en cambio es que la mayoría de las llamadas dentro de isFactor son referencias a funciones de clase, y no se comparten entre múltiples invocaciones de isFactor. Entonces, hay una gran sobrecarga de diccionario que es completamente innecesaria.

Esto casi nunca es un problema porque incluso la configuración predeterminada del compilador lo optimizará, sin embargo, parece que runhaskell ni siquiera hace tanto. Aun así, ocasionalmente he estructurado el código como someFun x y = \z -> porque sé que someFun se aplicará parcialmente y esta fue la única forma de preservar el uso compartido entre llamadas (es decir, el optimizador de GHC no fue lo suficientemente inteligente).

5

Según tengo entendido, runhaskell hace poca o ninguna optimización. Está diseñado para cargar y ejecutar código rápidamente. Si hiciera más optimización, tomaría más tiempo para que su código comience a ejecutarse. Por supuesto, en este caso, el código se ejecuta más rápido con optimizaciones.

Según tengo entendido, si existe una versión compilada del código, entonces runhaskell lo usará. Entonces, si el rendimiento es importante para usted, solo asegúrese de compilar con las optimizaciones activadas primero. (Creo que incluso podría ser capaz de pasar a runhaskell interruptores para encender optimizaciones - que tendría que revisar la documentación ...)

+0

¡Gracias por la respuesta! Entiendo que runhaskell no realiza muchas optimizaciones, pero en mi cabeza creo que 'foo x = bar x' y' foo = bar' deberían generar exactamente el mismo código. De ahí proviene mi confusión. Incluso sin optimizaciones, solo trato de entender por qué esos dos alguna vez funcionarían de manera diferente. – Steve

+1

Me imagino que hay una pequeña diferencia en cómo se estructuran los thunks dependiendo de qué forma se defina la función. Algo así como una versión comparte un thunk con todas las invocaciones, mientras que la otra hace una copia para cada invocación, lo que lleva a que ocurra mucha más asignación y desasignación. Eso sería mi _guess_ de todos modos; tendrías que preguntarle a los desarrolladores de GHC por la razón "real". Obviamente, con las optimizaciones activadas, ambas formas deberían producir código idéntico. Es solo que sin el pase de optimización, eso no sucede. – MathematicalOrchid

+0

Todas las optimizaciones se desactivan tanto en ghci como en runhaskell AFAIK, ya que el intérprete no admite valores no compartidos. – fuz

Cuestiones relacionadas