Making Sieve of Eratosthenes more memory efficient in python?

前端 未结 5 755
心在旅途
心在旅途 2021-01-20 00:41

Sieve of Eratosthenes memory constraint issue

Im currently trying to implement a version of the sieve of eratosthenes for a Kattis problem, however, I am running in

5条回答
  •  无人共我
    2021-01-20 01:07

    There's a trick I learned just yesterday - if you divide the numbers into groups of 6, only 2 of the 6 may be prime. The others can be evenly divided by either 2 or 3. That means it only takes 2 bits to track the primality of 6 numbers; a byte containing 8 bits can track primality for 24 numbers! This greatly diminishes the memory requirements of your sieve.

    In Python 3.7.5 64 bit on Windows 10, the following code didn't go over 36.4 MB.

    remainder_bit = [0, 0x01, 0, 0, 0, 0x02,
                     0, 0x04, 0, 0, 0, 0x08,
                     0, 0x10, 0, 0, 0, 0x20,
                     0, 0x40, 0, 0, 0, 0x80]
    
    def is_prime(xs, a):
        if a <= 3:
            return a > 1
        index, rem = divmod(a, 24)
        bit = remainder_bit[rem]
        if not bit:
            return False
        return not (xs[index] & bit)
    
    def sieve_of_eratosthenes(xs, n):
        count = (n // 3) + 1 # subtract out 1 and 4, add 2 3 and 5
        p = 5
        while p*p <= n:
            if is_prime(xs, p):
                for i in range(5 * p, n + 1, p):
                    index, rem = divmod(i, 24)
                    bit = remainder_bit[rem]
                    if bit and not (xs[index] & bit):
                        xs[index] |= bit
                        count -= 1
            p += 2
            if is_prime(xs, p):
                for i in range(5 * p, n + 1, p):
                    index, rem = divmod(i, 24)
                    bit = remainder_bit[rem]
                    if bit and not (xs[index] & bit):
                        xs[index] |= bit
                        count -= 1
            p += 4
    
        return count
    
    
    def init_sieve(n):
        return bytearray((n + 23) // 24)
    
    n = 100000000
    xs = init_sieve(n)
    sieve_of_eratosthenes(xs, n)
    5761455
    sum(is_prime(xs, i) for i in range(n+1))
    5761455
    

    Edit: the key to understanding how this works is that a sieve creates a repeating pattern. For the primes 2 and 3 the pattern repeats every 2*3 or 6 numbers, and of those 6, 4 have been rendered impossible to be prime leaving only 2. There's nothing limiting you in the choices of prime numbers to generate the pattern, except perhaps for the law of diminishing returns. I decided to try adding 5 to the mix, making the pattern repeat every 2*3*5=30 numbers. Out of these 30 numbers only 8 can be prime, meaning each byte can track 30 numbers instead of the 24 above! That gives you a 20% advantage in memory usage.

    Here's the updated code. I also simplified it a bit and took out the counting of primes as it went along.

    remainder_bit30 = [0,    0x01, 0,    0,    0,    0,    0, 0x02, 0,    0,
                       0,    0x04, 0,    0x08, 0,    0,    0, 0x10, 0,    0x20,
                       0,    0,    0,    0x40, 0,    0,    0, 0,    0,    0x80]
    
    def is_prime(xs, a):
        if a <= 5:
            return (a > 1) and (a != 4)
        index, rem = divmod(a, 30)
        bit = remainder_bit30[rem]
        return (bit != 0) and not (xs[index] & bit)
    
    def sieve_of_eratosthenes(xs):
        n = 30 * len(xs) - 1
        p = 0
        while p*p < n:
            for offset in (1, 7, 11, 13, 17, 19, 23, 29):
                p += offset
                if is_prime(xs, p):
                    for i in range(p * p, n + 1, p):
                        index, rem = divmod(i, 30)
                        if index < len(xs):
                            bit = remainder_bit30[rem]
                            xs[index] |= bit
                p -= offset
            p += 30
    
    def init_sieve(n):
        b = bytearray((n + 30) // 30)
        return b
    

提交回复
热议问题