Another microbenchmark: Why is this “loop” (compiled with ghc -O2 -fllvm, 7.4.1, Linux 64bit 3.2 kernel, redirected to /dev/null)
mapM_ print [1..100000000]
about 5x slower than a simple for-cycle in plain C with write(2) non-buffered syscall? I am trying to gather Haskell gotchas.
Even this slow C solution is much faster than Haskell
int i;
char buf[16];
for (i=0; i<=100000000; i++) {
sprintf(buf, "%d\n", i);
write(1, buf, strlen(buf));
}
Okay, on my box the C code, compiled per
gcc -O3takes about 21.5 seconds to run, the original Haskell code about 56 seconds. So not a factor of 5, a bit above 2.5.The first nontrivial difference is that
uses
Integers, that’s a bit slower because it involves a check upfront, and then works with boxedInts, while theShowinstance ofIntdoes the conversion work on unboxedInt#s.Adding a type signature, so that the Haskell code works on
Ints,brings the time down to 47 seconds, a bit above twice the time the C code takes.
Now, another big difference is that
showproduces a linked list ofCharand doesn’t just fill a contiguous buffer of bytes. That is slower too.Then that linked list of
Chars is used to fill a byte buffer that then is written to thestdouthandle.So, the Haskell code does more, and more complicated things than the C code, thus it’s not surprising that it takes longer.
Admittedly, it would be desirable to have an easy way to output such things more directly (and hence faster). However, the proper way to handle it is to use a more suitable algorithm (that applies to C too). A simple change to
almost halves the time taken, and if one wants it really fast, one uses the faster
ByteStringI/O and builds the output efficiently as exemplified in applicative’s answer.