4

In Wikipedia this is one of the given algorithms to generate prime numbers:

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only candidates below sqrt(n) need be checked. 
    candidates = [i for i in range(n + 1)]
    fin = int(n ** 0.5)

    # Loop over the candidates, marking out each multiple.
    for i in range(2, fin + 1):
        if not candidates[i]:
            continue

        candidates[i + i::i] = [None] * (n // i - 1)

    # Filter out non-primes and return the list.
    return [i for i in candidates[2:] if i]

I changed the algorithm slightly.

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only candidates below sqrt(n) need be checked. 
    candidates = [i for i in range(n + 1)]
    fin = int(n ** 0.5)

    # Loop over the candidates, marking out each multiple.

    candidates[4::2] = [None] * (n // 2 - 1)

    for i in range(3, fin + 1, 2):
        if not candidates[i]:
            continue

        candidates[i + i::i] = [None] * (n // i - 1)

    # Filter out non-primes and return the list.
    return [i for i in candidates[2:] if i]

I first marked off all the multiples of 2, and then I considered odd numbers only. When I timed both algorithms (tried 40.000.000) the first one was always better (albeit very slightly). I don't understand why. Can somebody please explain?

P.S.: When I try 100.000.000, my computer freezes. Why is that? I have Core Duo E8500, 4GB RAM, Windows 7 Pro 64 Bit.

Update 1: This is Python 3.

Update 2: This is how I timed:

start = time.time()
a = eratosthenes_sieve(40000000)
end = time.time()
print(end - start)

UPDATE: Upon valuable comments (especially by nightcracker and Winston Ewert) I managed to code what I intended in the first place:

def eratosthenes_sieve(n):
    # Create a candidate list within which non-primes will be
    # marked as None; only c below sqrt(n) need be checked. 
    c = [i for i in range(3, n + 1, 2)]
    fin = int(n ** 0.5) // 2

    # Loop over the c, marking out each multiple.

    for i in range(fin):
        if not c[i]:
            continue

        c[c[i] + i::c[i]] = [None] * ((n // c[i]) - (n // (2 * c[i])) - 1)

    # Filter out non-primes and return the list.
    return [2] + [i for i in c if i]

This algorithm improves the original algorithm (mentioned at the top) by (usually) 50%. (Still, worse than the algorithm mentioned by nightcracker, naturally).

A question to Python Masters: Is there a more Pythonic way to express this last code, in a more "functional" way?

UPDATE 2: I still couldn't decode the algorithm mentioned by nightcracker. I guess I'm too stupid.

Community
  • 1
  • 1
blackened
  • 387
  • 6
  • 15

4 Answers4

2

The question is, why would it even be faster? In both examples you are filtering multiples of two, the hard way. It doesn't matter whether you hardcode candidates[4::2] = [None] * (n // 2 - 1) or that it gets executed in the first loop of for i in range(2, fin + 1):.

If you are interested in an optimized sieve of Eratosthenes, here you go:

def primesbelow(N):
    # https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    #""" Input N>=6, Returns a list of primes, 2 <= p < N """
    correction = N % 6 > 1
    N = (N, N-1, N+4, N+3, N+2, N+1)[N%6]
    sieve = [True] * (N // 3)
    sieve[0] = False
    for i in range(int(N ** .5) // 3 + 1):
        if sieve[i]:
            k = (3 * i + 1) | 1
            sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
            sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
    return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]

Explanation here: Porting optimized Sieve of Eratosthenes from Python to C++

The original source is here, but there was no explanation. In short this primesieve skips multiples of 2 and 3 and uses a few hacks to make use of fast Python assignment.

Community
  • 1
  • 1
orlp
  • 98,226
  • 29
  • 187
  • 285
  • Thanks for your input and sparing your time. I bypass all the even numbers. Isn't that significant? – blackened Mar 24 '11 at 22:48
  • 1
    @blackened: The problem is you don't bypass the even numbers. You allocate storage for them, and you even filter them out, wasting precious CPU time! Why give stinky even numbers this special treatment they don't deserve? If you would bypass the even numbers you would create an array with `^[N/2]` (^[] is rounding up) elements. The element at the key `i` would mark the number `2*i + 1` prime or non-prime. – orlp Mar 24 '11 at 22:53
  • I think you are right. I'll think about your comment. Thanks for sparing your time. – blackened Mar 24 '11 at 23:01
  • It's basicly this: http://stackoverflow.com/questions/5293238/porting-optimized-sieve-of-eratosthenes-from-python-to-c, but then with a hack that uses slice assignments so the python interpreter uses fast in-place assignments (in C++ this is not needed since you can do those assignments yourself). – orlp Mar 24 '11 at 23:01
2

You do not save a lot of time avoiding the evens. Most of the computation time within the algorithm is spent doing this:

candidates[i + i::i] = [None] * (n // i - 1)

That line causes a lot of action on the part of the computer. Whenever the number in question is even, this is not run as the loop bails on the if statement. The time spent running the loop for even numbers is thus really really small. So eliminating those even rounds does not produce a significant change in the timing of the loop. That's why your method isn't considerably faster.

When python produces numbers for range it uses a formula: start + index * step. Multiplying by two as you do in your case is going to be slightly more expensive then one as in the original case.

There is also quite possibly a small overhead to having a longer function.

Neither are of those are really significant speed issues, but they override the very small amount of benefit your version brings.

Winston Ewert
  • 40,727
  • 10
  • 63
  • 79
0

Its probably slightly slower because you are performing extra set up to do something that was done in the first case anyway (marking off multiples of two). That setup time might be what you see if it is as slight as you say

kniteli
  • 491
  • 3
  • 10
  • He's trying to get an advantage by passing 2 to final of xrange thereby making the looping faster. i.e. He can skip the check of primeness for all even numbers. – Winston Ewert Mar 24 '11 at 22:37
  • Well, against that, the first code iterates all integers from 2 to sqrt(n), mine iterates half of it. – blackened Mar 24 '11 at 22:37
0

Your extra step is unnecessary and will actually traverse the whole collection n once doing that 'get rid of evens' operation rather than just operating on n^1/2.

Ichorus
  • 4,447
  • 6
  • 34
  • 46