Comparing performance of two programming languages is a dangerous topic in some sense. It is easy to trigger the meaningless “Religious War” of whether one language is fundamentally better than another. Meanwhile, performance of a program can be affected by so many factors: speed of hardware and network, algorithm, quality of third-party libraries, operating systems and compilers, etc. The language program is written in is often not a main reason.

However, recently when I start learning Haskell, I encountered a problem that the compiled Haskell binary runs much slower and costs more resources than an interpreted Perl script using the same algorithm.

The Story

The problem can be found on Codeforces. It is the first problem on Facebook Hackercup 2015, named “Homework”. It is an easy problem using sieve algorithm to print out in range [A,B], how many numbers has primacy K, where K is the number of prime factors a number has.

For example, 12 has primacy 2, because it has two prime factors: 2 and 3.

7 has primacy 1, since it has only one prime factor: 7.

140 has primacy 3, which has three prime factors: 2, 5, 7.

I quickly come up with a Perl solution and got accepted.

Then I decide to rewrite it using Haskell and here is my first try. To practice my functional programming skill, I try to keep everything pure and functional and nothing imperative. The algorithm is the same with Perl one, at least that is what I think when I finish.

To my surprise, the Haskell solution runs much slower than Perl script and costs even more memories (which triggers memory exceed limit error on Codeforces OJ). I tried to optimize it by removing unnecessary data structures, but seems everything is needed and I cannot do much about it.

So I post a question on StackOverflow. I get an answer just verified my suspect: in Haskell there is no convenient way to update or map partially to a List, simply because List is immutable. Every time you want to modify even one element of it, you will need to make a full copy of the whole list, visiting each element of the list and make a copy, which leads to huge performance overhead in this case.

I admit Haskell’s pure functional programming idea is novel, appealing and terse, however, overhead is overhead. Unfortunately, the solution Haskell currently provides almost beats all its elegancy and my initial motivation of learning Haskell: to give up functional style and use imperative data structures like STUArray. I will have to write an imperative function to get the main result, making my new Haskell program looking more like its Perl counterpart.

What I learn from this

I have no doubt about Haskell’s fantastic features: immutable data structures, lazy evaluation, high-order functions and monad-style error handling. It is definitely a programming language more advanced than its rivals. However this case also shows us the disadvantage or shortages in its design decision. Sometimes you will have to “roll back” to imperative programming style and get your hands dirty to deal with details, because compiler is still not smart enough to do every possible optimization human does.

However, programmers are not oracles. They cannot predict when imperative is needed for performance, when is not. Just imagine switching all related appearance of List to STUArray in a big project or program. Also, style switching between imperative and functional is not free – it comes with memory copy overhead. This is the problem Haskell introduces when it adds all these nice properties of functional programming. If you decide to use Haskell for your project, you got to be careful about it.