Alerta de alerta: esto está relacionado con Problem 14 de Project Euler.¿Por qué es tan simple este algoritmo haskell tan lento?
El siguiente código tarda unos 15 segundos en ejecutarse. Tengo una solución Java no recursiva que se ejecuta en 1s. Creo que debería poder obtener este código mucho más cerca de eso.
import Data.List
collatz a 1 = a
collatz a x
| even x = collatz (a + 1) (x `div` 2)
| otherwise = collatz (a + 1) (3 * x + 1)
main = do
print ((foldl1' max) . map (collatz 1) $ [1..1000000])
he perfilado con +RHS -p
y se dio cuenta de que la memoria asignada es grande, y crece a medida que crece la entrada. Para n = 100,000
se asigna 1gb (!), Para n = 1,000,000
se asignan 13 gb (!!).
Por otra parte, -sstderr
muestra que, aunque se asignaron muchos bytes, el uso de la memoria total fue de 1 mb, y la productividad fue del 95% +, por lo que tal vez 13 gb son una auténtica locura.
Se me ocurren algunas posibilidades:
Algo no es tan estricta como tiene que ser. Ya descubrí
foldl1'
, pero ¿quizás necesito hacer más? ¿Es posible marcarcollatz
tan estrictas (¿eso siquiera tiene sentido?collatz
no es la optimización de llamada final. Creo que debería ser, pero no saben una manera de confirmar.El compilador no está haciendo algunas optimizaciones creo que debería - por ejemplo solamente dos resultados de
collatz
tienen que estar en la memoria en un momento dado (máximo y actual)
cualquier sugerencia
?Esto es más o menos un duplicado de Why is this Haskell expression so slow?, aunque señalaré que la solución rápida de Java no tiene que realizar ninguna memoria. ¿Hay alguna forma de acelerar esto sin tener que recurrir a él?
Para referencia, aquí es mi producción de perfiles:
Wed Dec 28 09:33 2011 Time and Allocation Profiling Report (Final)
scratch +RTS -p -hc -RTS
total time = 5.12 secs (256 ticks @ 20 ms)
total alloc = 13,229,705,716 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
collatz Main 99.6 99.4
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 1 0 0.0 0.0 100.0 100.0
CAF Main 208 10 0.0 0.0 100.0 100.0
collatz Main 215 1 0.0 0.0 0.0 0.0
main Main 214 1 0.4 0.6 100.0 100.0
collatz Main 216 0 99.6 99.4 99.6 99.4
CAF GHC.IO.Handle.FD 145 2 0.0 0.0 0.0 0.0
CAF System.Posix.Internals 144 1 0.0 0.0 0.0 0.0
CAF GHC.Conc 128 1 0.0 0.0 0.0 0.0
CAF GHC.IO.Handle.Internals 119 1 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 113 5 0.0 0.0 0.0 0.0
Y -sstderr:
./scratch +RTS -sstderr
525
21,085,474,908 bytes allocated in the heap
87,799,504 bytes copied during GC
9,420 bytes maximum residency (1 sample(s))
12,824 bytes maximum slop
1 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 40219 collections, 0 parallel, 0.40s, 0.51s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 35.38s (36.37s elapsed)
GC time 0.40s ( 0.51s elapsed)
RP time 0.00s ( 0.00s elapsed) PROF time 0.00s ( 0.00s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 35.79s (36.88s elapsed) %GC time 1.1% (1.4% elapsed) Alloc rate 595,897,095 bytes per MUT second
Productivity 98.9% of total user, 95.9% of total elapsed
y la solución de Java (no la mía, tomada de los foros Proyecto Euler con memoization eliminado):
public class Collatz {
public int getChainLength(int n)
{
long num = n;
int count = 1;
while(num > 1)
{
num = (num%2 == 0) ? num >> 1 : 3*num+1;
count++;
}
return count;
}
public static void main(String[] args) {
Collatz obj = new Collatz();
long tic = System.currentTimeMillis();
int max = 0, len = 0, index = 0;
for(int i = 3; i < 1000000; i++)
{
len = obj.getChainLength(i);
if(len > max)
{
max = len;
index = i;
}
}
long toc = System.currentTimeMillis();
System.out.println(toc-tic);
System.out.println("Index: " + index + ", length = " + max);
}
}
Es bastante sorprendente que GHC no optimice (quot n 2) a (rshift n 1) como se esperaría que hiciera cualquier compilador de C que se precie. ¿Hay una razón? –
@solrize: Me sorprendió también. – ehird