[Haskell-cafe] Code and Perf. Data for Prime Finders (was: Genuine Eratosthenes sieve)

Melissa O'Neill oneill at cs.hmc.edu
Thu Feb 22 01:54:38 EST 2007


I'd like to bring the thread that began with a question about ``why  
the "classic sieve" example did so badly when compared to "a C  
implementation of the same thing"'' back to its starting point.  In  
the discussion that ensued, various people have spoken about various  
algorithms to find primes.  I've mentioned my own, and claimed it was  
a more faithful rendition of the Sieve of Eratosthenes in a  
functional setting (where we like to generalize it to produce a lazy  
and infinite list) than the classic one-liner.

But talk is cheap.  What about some actual numbers, and some code for  
some actual implementations...?

I've put together an archive of all the code we've talked about, and  
put it at

    http://www.cs.hmc.edu/~oneill/code/haskell-primes.zip

Here are some performance measurements of the primes algorithms we've  
been discussing, with the task of finding the 1000th prime (7919),  
the 10,000th prime (104729), the 100,000th prime (1299709) and the  
1,000,000th prime (15485863).  The times are with GHC on my PowerMac G5.

   (this table best viewed in a fixed-width font!)
    --------------------------------------------------------
                  Time (s) for Number of Primes
                  ------------------------------------------
    Algorithm     10^3    10^4      10^5    10^6     10^7
    --------------------------------------------------------
    O'Neill (#2)  0.01      0.09     1.45    22.41    393.28
    O'Neill (#1)  0.01      0.14     2.93    47.08    -
    "sieve" (#3)  0.01      0.25     7.28   213.19    -
    Naive         0.32      0.66    16.04   419.22    -
    Runciman      0.02      0.74    29.25   -         -
    Reinke        0.04      1.21    41.00   -         -
    Gale (#1)     0.12     17.99    -       -         -
    "sieve" (#1)  0.16     32.59    -       -         -
    "sieve" (#2)  0.01     32.76    -       -         -
    Gale (#2)     1.36    268.65    -       -         -
    --------------------------------------------------------

Notes:
- The dashes in the table mean "I gave up waiting" (i.e., > 500 seconds)
- "sieve" (#1) is the classic example we're all familiar with
- "sieve" (#2) is the classic example, but sieving a list without  
multiples of 2,3,5, or 7 -- notice how it makes no real difference
- "sieve" (#3) is the classic example, but generating a lazy-but- 
finite list (see below)
- O'Neill (#1) is the algorithm of mine discussed in http:// 
www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
- O'Neill (#2) is a variant of that algorithm that generates a lazy- 
but-finite list of primes.
- Naive is the the non-sieve-based "divide by every prime up to the  
square root" algorithm for finding primes
- Runciman is Colin Runciman's algorithm, from his _Lazy Wheel Sieves  
and Spirals of Primes_ paper
- Reinke is the ``applyAt'' algorithm Claus Reinke posted here
- Gale (#1) is Yitz Gale's deleteOrd algorithm
- Gale (#2) is Yitz Gale's crossOff algorithm

Returning to where we began, if you want to calculate primes in  
Haskell, there are ways to do it -- ways based on the Sieve of  
Eratosthenes -- where you don't have to feel too bad about the  
performance of functional programs.

     Melissa.

P.S.  Here's the code for a "sieve" (#3) style prime finder.

primesToLimit :: Integer -> [Integer]
primesToLimit limit = 2 : lsieve [3,5..] where
     lsieve ps@(p : xs)
         | p < sqrtlimit = p : lsieve [x | x <- xs, x `mod` p > 0]
         | otherwise     = takeWhile (<= limit) ps
         where
             sqrtlimit = round (sqrt (fromInteger limit))



More information about the Haskell-Cafe mailing list