Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple tips for Haskell performance increases (on ProjectEuler problems)?

I'm new to programming and learning Haskell by reading and working through Project Euler problems. Of course, the most important thing one can do to improve performance on these problems is to use a better algorithm. However, it is clear to me that there are other simple and easy to implement ways to improve performance. A cursory search brought up this question, and this question, which give the following tips:

  • Use the ghc flags -O2 and -fllvm.
  • Use the type Int, instead of Integer, because it is unboxed (or even Integer instead of Int64). This requires typing the functions, not letting the compiler decide on the fly.
  • Use rem, not mod, for division testing.
  • Use Schwartzian transformations when appropriate.
  • Using an accumulator in recursive functions (a tail-recursion optimization, I believe).
  • Memoization (?)

(One answer also mentions worker/wrapper transformation, but that seems fairly advanced.)

Question: What other simple optimizations can one make in Haskell to improve performance on Project Euler-style problems? Are there any other Haskell-specific (or functional programming specific?) ideas or features that could be used to help speed up solutions to Project Euler problems? Conversely, what should one watch out for? What are some common yet inefficient things to be avoided?

like image 431
Potato Avatar asked Jul 14 '12 07:07

Potato


4 Answers

Here are some good slides by Johan Tibell that I frequently refer to:

Haskell Performance Patterns

like image 86
jtobin Avatar answered Oct 13 '22 10:10

jtobin


One easy suggestion is to use hlint which is a program that checks your source code and makes suggestions for improvements syntax wise. This might not increase speed because most likely it's already done by the compiler or the lazy evaluation. But it might help the compiler in some cases. Further more it will make you a better Haskell programmer since you will learn better ways to do things, and it might be easier to understand your program and analyze it.

examples taken from http://community.haskell.org/~ndm/darcs/hlint/hlint.htm such as:

darcs-2.1.2\src\CommandLine.lhs:94:1: Error: Use concatMap
Found:
  concat $ map escapeC s
Why not:
  concatMap escapeC s

and

darcs-2.1.2\src\Darcs\Patch\Test.lhs:306:1: Error: Use a more efficient monadic variant
Found:
  mapM (delete_line (fn2fp f) line) old
Why not:
  mapM_ (delete_line (fn2fp f) line) old

I think the largest increases you can do in Project Euler problems is to understand the problem and remove unnecessary computations. Even if you don't understand everything you can do some small fixes which will make your program run twice the speed. Let's say you are looking for primes up to 1.000.000, then you of course can do filter isPrime [1..1000000]. But if you think a bit, then you can realize that well, no even number above is a prime, there you have removed (about) half the work. Instead doing [1,2] ++ filter isPrime [3,5..999999]

like image 34
Viktor Mellgren Avatar answered Oct 13 '22 10:10

Viktor Mellgren


There is a fairly large section of the Haskell wiki about performance.

One fairly common problem is too little (or too much) strictness (this is covered by the sections listed in the General techniques section of the performance page above). Too much laziness causes a large number of thunks to be accumulated, too much strictness can cause too much to be evaluated.

These considerations are especially important when writing tail recursive functions (i.e. those with an accumulator); And, on that note, depending on how the function is used, a tail recursive function is sometimes less efficient in Haskell than the equivalent non-tail-recursive function, even with the optimal strictness annotations.

Also, as demonstrated by this recent question, sharing can make a huge difference to performance (in many cases, this can be considered a form of memoisation).

like image 38
huon Avatar answered Oct 13 '22 10:10

huon


Project Euler is mostly about finding clever algorithmic solutions to the problems. Once you have the right algorithm, micro-optimization is rarely an issue, since even a straightforward or interpreted (e.g. Python or Ruby) implementation should run well within the speed constraints. The main technique you need is understanding lazy evaluation so you can avoid thunk buildups.

like image 32
solrize Avatar answered Oct 13 '22 12:10

solrize