I’m working through the problems in Project Euler as a way of learning Haskell, and I find that my programs are a lot slower than a comparable C version, even when compiled. What can I do to speed up my Haskell programs?
For example, my brute-force solution to Problem 14 is:
import Data.Int
import Data.Ord
import Data.List
searchTo = 1000000
nextNumber :: Int64 -> Int64
nextNumber n
| even n = n `div` 2
| otherwise = 3 * n + 1
sequenceLength :: Int64 -> Int
sequenceLength 1 = 1
sequenceLength n = 1 + (sequenceLength next)
where next = nextNumber n
longestSequence = maximumBy (comparing sequenceLength) [1..searchTo]
main = putStrLn $ show $ longestSequence
Which takes around 220 seconds, while an “equivalent” brute-force C version only takes 1.2 seconds.
#include <stdio.h>
int main(int argc, char **argv)
{
int longest = 0;
int terms = 0;
int i;
unsigned long j;
for (i = 1; i <= 1000000; i++)
{
j = i;
int this_terms = 1;
while (j != 1)
{
this_terms++;
if (this_terms > terms)
{
terms = this_terms;
longest = i;
}
if (j % 2 == 0)
j = j / 2;
else
j = 3 * j + 1;
}
}
printf("%d\n", longest);
return 0;
}
What am I doing wrong? Or am I naive to think that Haskell could even approach C’s speed?
(I’m compiling the C version with gcc -O2, and the Haskell version with ghc –make -O).
For testing purpose I have just set
searchTo = 100000. The time taken is 7.34s. A few modification leads to some big improvement:Use an
Integerinstead ofInt64. This improves the time to 1.75s.Use an accumulator (you don’t need sequenceLength to be lazy right?) 1.54s.
Rewrite the
nextNumberusingquotRem, thus avoiding computing the division twice (once inevenand once indiv). 1.27s.Use Schwartzian transform instead of
maximumBy. The problem ofmaximumBy . comparingis that thesequenceLengthfunction is called more than once for each value. 0.32s.Note:
ghc -Oand run with+RTS -s)gcc -O3 -m32.