4

Disclaimer: I'm working on Euler Problem 9.

I'm adding up some pretty large numbers, all the primes from 1 to 2 000 000.

Summing those primes takes forever. I'm using the haskell built in function 'sum'.

as in:

sum listOfPrimes

Are there any other faster options?

--My prime generator was the slow link in my code.

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
cbrulak
  • 15,436
  • 20
  • 61
  • 101

4 Answers4

11

It sounds like your problem is not summing the numbers, but generating them. What is your implementation of listOfPrimes?

This paper may be of interest: http://lambda-the-ultimate.org/node/3127

Edward Z. Yang
  • 26,325
  • 16
  • 80
  • 110
9

I'm hoping you are using ghc -O2 and not ghci, right? Your problem will be in the generation, not the summation.

One faster way is to use stream fusion-based sequences, which optimize better. With regular lists:

import Data.List
import qualified Data.Map as M

primes :: [Integer]
primes = mkPrimes 2 M.empty
  where
    mkPrimes n m = case (M.null m, M.findMin m) of
        (False, (n', skips)) | n == n' ->
            mkPrimes (succ n) (addSkips n (M.deleteMin m) skips)
        _ -> n : mkPrimes (succ n) (addSkip n m n)
    addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m
    addSkips = foldl' . addSkip

-- fuse:
main = print (sum (takeWhile (<= 2000000) primes))

We get,

$ ghc -O2 --make A.hs
$ time ./A           
142913828922
./A  9.99s user 0.17s system 99% cpu 10.166 total

Switching to streams, so sum . takeWhile fuses:

import qualified Data.List.Stream as S
main = print (S.sum (S.takeWhile (<= 2000000) primes))

Saves some small time,

$ time ./A           
142913828922
./A  9.60s user 0.13s system 99% cpu 9.795 total

But your problem will be prime generation, as we can see if we discard the summation altogether, replacing sum with last:

$ time ./A           
1999993
./A  9.65s user 0.12s system 99% cpu 9.768 total

So find a better prime generator. :-)

Finally, there's a library on Hackage for fast prime generators:

http://hackage.haskell.org/packages/archive/primes/0.1.1/doc/html/Data-Numbers-Primes.html

Using it, our time becomes:

$ cabal install primes
$ cabal install stream-fusion

$ cat A.hs
import qualified Data.List.Stream as S
import Data.Numbers.Primes

main = print . S.sum . S.takeWhile (<= 2000000) $ primes

$ ghc -O2 -fvia-C -optc-O3 A.hs --make

$ time ./A
142913828922
./A  0.62s user 0.07s system 99% cpu 0.694 total
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • Since you didn't link to it, http://www.cse.unsw.edu.au/~dons/streams.html | Pretty cool stuff, I didn't realize it was nicely packaged up on Hackage. – ephemient Jul 16 '09 at 21:08
7

I wrote a "Sieve of Eratosthenes" here:

import Data.List
import qualified Data.Map as M
primes :: [Integer]
primes = mkPrimes 2 M.empty
  where
    mkPrimes n m = case (M.null m, M.findMin m) of
        (False, (n', skips)) | n == n' ->
            mkPrimes (succ n) (addSkips n (M.deleteMin m) skips)
        _ -> n : mkPrimes (succ n) (addSkip n m n)
    addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m
    addSkips = foldl' . addSkip

Using this, it takes about 25s to print . sum $ takeWhile (<= 20000000) on my desktop. Could be better? Sure, it takes J under 1 second to run

   +/p:i.p:^:_1]20000000
12272577818052

but it has a pretty optimized prime number generator.

Community
  • 1
  • 1
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 2
    very nice and clear and good! Three standard improvements - odds only, postponement of adding primes into map, and double primes feed - make it run 10.6x faster for 2mln (tested), and ~ 13.4x for 20 mln (projected). So the run time would become ~1.9s. I've added the changed code to [the haskellwiki page](http://www.haskell.org/haskellwiki/Prime_numbers#Map-based). :) – Will Ness Dec 21 '12 at 19:54
  • 1
    @WillNess Best reply ever :-) – ephemient Dec 22 '12 at 08:15
  • why, thank you very much. :) ... forgot to mention it runs now in near constant space, 1..2 MB. but thats expected. – Will Ness Dec 22 '12 at 08:29
7

The slow part of your function is for sure the generating of the primes, not the sum function. A nice way to generate primes would be:

isprime :: (Integral i) => i -> Bool
isprime n = isprime_ n primes
  where isprime_ n (p:ps)
          | p*p > n        = True
          | n `mod` p == 0 = False
          | otherwise      = isprime_ n ps

primes :: (Integral i) => [i]
primes = 2 : filter isprime [3,5..]

I think it's very readable, though maybe a bit surprising that it works at all because it uses recursion and laziness of the primes list. It's also rather fast, though one could do further optimizations at the expense of readability.

sth
  • 222,467
  • 53
  • 283
  • 367
  • Hey, I haven't seen this particular algorithm in Haskell before. Pretty cool, it's simpler and and feels more Haskell-y than mine. – ephemient Jul 17 '09 at 00:30
  • 1
    This is actually equivalent to the inefficient algorithm. It still tests each number against each prime. – newacct Jul 17 '09 at 04:28
  • 2
    @newacc: It uses the "inefficient" algorithm, but it's *faster* then the "efficient" version, at least in this case. The "efficient" algorithm needs to do lots of map modifications and constantly iterates over bigger and bigger maps. That's not necessarily better that comparing against a few primes. – sth Jul 17 '09 at 08:11
  • 1
    Exactly. It feels just as simple and clean as the traditional `primes = sieve [2..] where sieve (p:xs) = p:[x | x <- xs, mod x p /= 0]` -- in fact, it's equivalent -- but operates more efficiently. – ephemient Jul 17 '09 at 16:17
  • 1
    this is an (almost) optimal trial division (OTD) sieve, *radically* more efficient than the "traditional" "inefficient" algorithm i.e. the sieve of Turner `ps=sv[2..] where sv(p:xs)=p:sv[x|x<-xs,rem x p>0]`, because it stops the testing for an `n` at its square root - Turner's tests by all primes below the number itself. The theoretical time complexity for OTD is `O(k^1.5/(log k)^0.5)` (empirically seen usually as `~ k^1.45`), and for Turner's - `O(k^2)`, in `k` primes produced. So in producing a million primes OTD will be `~ 2000x ... 4000x` (or more) faster than Turner's sieve. – Will Ness Dec 22 '12 at 12:38