I originally wrote this method (brute force and inefficiency) to calculate primes in order to make sure that there is no difference in speed between using "if-then-else" and protection in Haskell (and there is no difference!). But then I decided to write a C program for comparison, and I got the following (Haskell is slower just over 25%):
(Note: I got the idea of ββusing rem instead of mod, as well as the O3 option in calling the compiler from the following message: On improving Haskell performance compared to C in the Fibonacci micro test )
Haskell: Forum.hs
divisibleRec :: Int -> Int -> Bool divisibleRec ij | j == 1 = False | i `rem` j == 0 = True | otherwise = divisibleRec i (j-1) divisible::Int -> Bool divisible i = divisibleRec i (i-1) r = [ x | x <- [2..200000], divisible x == False] main :: IO() main = print(length(r))
C: main.cpp
#include <stdio.h> bool divisibleRec(int i, int j){ if(j==1){ return false; } else if(i%j==0){ return true; } else{ return divisibleRec(i,j-1); } } bool divisible(int i){ return divisibleRec(i, i-1); } int main(void){ int i, count =0; for(i=2; i<200000; ++i){ if(divisible(i)==false){ count = count+1; } } printf("number of primes = %d\n",count); return 0; }
The results that I received were as follows:
Compilation time
time (ghc -O3 -o runProg Forum.hs) real 0m0.355s user 0m0.252s sys 0m0.040s time (gcc -O3 -o runProg main.cpp) real 0m0.070s user 0m0.036s sys 0m0.008s
and the following runtime:
Ubuntu 32-Bit Runtime
Haskell 17984 real 0m54.498s user 0m51.363s sys 0m0.140s C++ number of primes = 17984 real 0m41.739s user 0m39.642s sys 0m0.080s
I was impressed with the running time of Haskell. However, my question is this: can I do something to speed up the haskell program without:
- Changing the basic algorithm (it is clear that mass accelerations can be achieved by changing the algorithm, but I just want to understand what I can do on the language / compiler side to improve performance).
- Invoking the llvm compiler (because I don't have one)
[EDIT: memory usage]
After Alan's comment, I noticed that C uses a constant amount of memory, where as Haskell slowly grows in memory size. At first I thought this was related to recursion, but gspr explains below why this is happening and provides a solution. Will Ness provides an alternative solution that (e.g. gspr solution) also ensures that memory remains static.
[EDIT: Short Circulation Summary]
Maximum number tested: 200,000:
(54.498s / 41.739s) = Haskell 30.5% slower
Maximum number tested: 400,000:
3m31.372s / 2m45.076s = 211.37s / 165s = Haskell 28.1% slower
Maximum number of tests: 800,000:
14m3.266s / 11m6.024s = 843.27s / 666.02s = Haskell 26.6% slower
[EDIT: Code for Alan]
This was the code I wrote earlier that has no recursion and which I tested for 200,000:
#include <stdio.h> bool divisibleRec(int i, int j){ while(j>0){ if(j==1){ return false; } else if(i%j==0){ return true; } else{ j -= 1;} } } bool divisible(int i){ return divisibleRec(i, i-1); } int main(void){ int i, count =0; for(i=2; i<8000000; ++i){ if(divisible(i)==false){ count = count+1; } } printf("number of primes = %d\n",count); return 0; }
The results for C code with and without recursion are as follows (for 800,000):
With recursion: 11m6.024s
No recursion: 11m5.328s
Note that the executable seems to occupy 60kb (as seen on the system monitor), regardless of the maximum number, and therefore I suspect the compiler is detecting this recursion.