I was reading an article of how slow Haskell is in playing with Collatz conjecture, which basically states if you keep multiplying by three and adding one to an odd number, or dividing an even one with two, you will eventually get one. For instance, 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1.
The program given in this article is to calculate the longest Collatz sequence in a given range. The C version is:
#include <stdio.h>
int main(int argc, char **argv) {
int max_a0 = atoi(argv[1]);
int longest = 0, max_len = 0;
int a0, len;
unsigned long a;
for (a0 = 1; a0 <= max_a0; a0++) {
a = a0;
len = 0;
while (a != 1) {
len++;
a = ((a%2==0)? a : 3*a+1)/2;
}
if (len > max_len) {
max_len = len;
longest = a0;
}
}
printf("(%d, %d)\n", max_len, longest);
return 0;
}
Compiling with Clang O2, it runs on my computer for 0.2s.
The Haskell version given in that article generates the whole sequence as a list explicitly, and then calculate the length of the intermediate list. It is 10x slower than the C version. However, since the author used LLVM as a backend, which I haven't installed, I couldn't reproduce this. Using GHC 7.8 and the default backend, it runs 10s on my Mac, which is 50x slower than the C version.
Then, I wrote a version using tail recursion and not generating an intermediate list:
collatzNext :: Int -> Int
collatzNext a
| even a = a `div` 2
| otherwise = (3 * a + 1) `div` 2
collatzLen :: Int -> Int
collatzLen n = collatzIter n 0
where
collatzIter 1 len = len
collatzIter n len = collatzIter (collatzNext n) (len + 1)
main = do
print $ maximum $ [collatzLen x | x <- [1..1000000]]
Compiled with GHC 7.8 and O2, it runs for 2s, 10x slower than the C version.
Interestingly, when I changed Int
in the type annotation to Word
, it only spent 1s, 2x faster!
I have tried BangPatterns for explicit strict evaluation, but no significant performance gain could be noticed — I guess GHC's strict analysis is smart enough to handle such a simple scenario.
My questions are:
- Why is the
Word
version so much faster compared with theInt
one? - Why is this Haskell program so slow compared with that written in C?
./a 0.00s user 0.00s system 29% cpu 0.013 total
, when n = 1000000, the time is./a 1.14s user 0.01s system 99% cpu 1.157 total
. I also checked RTS's GC report, time used in GC is less than 1%. – Minsheng Liu2
is generally faster with unsigned int rather than signed int. And that may be the case why yourWord
instance is faster becauseWord
represents unsigned integer. – Sibidiv
function is slower for Int than Word. For speed you should usequot
with Int. – augustsscollatzNext
function is wrong here and in the linked blog post. It should be3 * n + 1
rather than(3 * n + 1) / 2
in the odd case. – András Kovács