Actualmente estoy tratando de optimizar mi solución a problem 14 en Projet Euler. me gusta mucho Haskell y yo creo que es una muy buena opción para este tipo de problemas, aquí hay tres soluciones diferentes que he probado:Almacenamiento en memoria caché en Haskell y paralelismo explícito
import Data.List (unfoldr, maximumBy)
import Data.Maybe (fromJust, isNothing)
import Data.Ord (comparing)
import Control.Parallel
next :: Integer -> Maybe (Integer)
next 1 = Nothing
next n
| even n = Just (div n 2)
| odd n = Just (3 * n + 1)
get_sequence :: Integer -> [Integer]
get_sequence n = n : unfoldr (pack . next) n
where pack n = if isNothing n then Nothing else Just (fromJust n, fromJust n)
get_sequence_length :: Integer -> Integer
get_sequence_length n
| isNothing (next n) = 1
| otherwise = 1 + (get_sequence_length $ fromJust (next n))
-- 8 seconds
main1 = print $ maximumBy (comparing length) $ map get_sequence [1..1000000]
-- 5 seconds
main2 = print $ maximum $ map (\n -> (get_sequence_length n, n)) [1..1000000]
-- Never finishes
main3 = print solution
where
s1 = maximumBy (comparing length) $ map get_sequence [1..500000]
s2 = maximumBy (comparing length) $ map get_sequence [500001..10000000]
solution = (s1 `par` s2) `pseq` max s1 s2
Ahora bien, si nos fijamos en el problema real que hay un gran potencial para el almacenamiento en caché , ya que la mayoría de las nuevas secuencias contendrán subsecuencias que ya se han calculado anteriormente.
Para la comparación, me escribió una versión en C también:
Tiempo de duración con el almacenamiento en caché: 0,03 segundos
El tiempo en marcha sin almacenamiento en caché: 0,3 segundos
Eso es una locura! Claro, el almacenamiento en caché redujo el tiempo en un factor de 10, pero incluso sin almacenamiento en caché, sigue siendo al menos 17 veces más rápido que mi código Haskell.
¿Qué pasa con mi código? ¿Por qué la memoria caché de Haskell no me llama? Como las funciones son puro caché, ¿no debería el almacenamiento en caché ser trivial, solo una cuestión de memoria disponible?
¿Cuál es el problema con mi tercera versión paralela? ¿Por qué no termina?
En cuanto a Haskell como lenguaje, ¿el compilador paralela automáticamente algún código (pliegues, mapas, etc.), o siempre tiene que hacerse explícitamente usando Control.Parallel?
Edit: Me encontré con this pregunta similar. Mencionaron que su función no era recursiva de la cola. ¿Mi cola get_sequence_length es recursiva? Si no, ¿cómo puedo hacerlo?
Edit2:
Para Daniel:
Muchas gracias por la respuesta, realmente impresionante. He estado jugando con tus mejoras y he encontrado algunos errores realmente malos.
Estoy ejecutando las pruebas en Windws 7 (64 bits), núcleo cuádruple de 3,3 GHZ con 8 GB de RAM.
Lo primero que hice fue como dices reemplazar todos los enteros con Int, pero cada vez que ejecuté alguno de los principales me quedé sin memoria, incluso con + RTS kSize -RTS establecido ridículamente alto.
Finalmente encontré this (stackoverflow es impresionante ...), lo que significa que, dado que todos los programas de Haskell en Windows se ejecutan como de 32 bits, los Entrs rebosaban causar una recursión infinita, guau ...
Ejecuté las pruebas en una máquina virtual Linux (con el ghc de 64 bits) y obtuve resultados similares.
Usted tiene un cero en 'main3' ... –