Ускорить bitstring/bit операции в Python?

Я написал генератор простых чисел, используя Сито Эратосфена и Python 3.1. Код работает корректно и грациозно на 0,32 секунды на ideone.com для генерации простых чисел до 1 000 000.

# from bitstring import BitString

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...    
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5) 
    flags = [False, False] + [True] * (limit - 2)   
#     flags = BitString(limit)
    # Step through all the odd numbers
    for i in range(3, limit, 2):       
        if flags[i] is False:
#        if flags[i] is True:
            continue
        yield i
        # Exclude further multiples of the current prime number
        if i <= sub_limit:
            for j in range(i*3, limit, i<<1):
                flags[j] = False
#                flags[j] = True

Проблема заключается в том, что у меня заканчивается память, когда я пытаюсь создать числа до 1 000 000 000.

    flags = [False, False] + [True] * (limit - 2)   
MemoryError

Как вы можете себе представить, выделение 1 миллиарда булевых значений ( 1 байт 4 или 8 байтов (см. комментарий) каждый в Python) действительно невозможно, поэтому я просмотрел bitstring. Я полагал, что использование 1 бит для каждого флага будет намного более эффективным для памяти. Однако производительность программы резко снизилась - 24 секунды в режиме ожидания, для простого номера до 1 000 000. Вероятно, это связано с внутренней реализацией bitstring.

Вы можете комментировать/раскомментировать три строки, чтобы увидеть, что я изменил, чтобы использовать BitString в качестве фрагмента кода выше.

Мой вопрос: есть способ ускорить мою программу, с или без bitstring?

Изменить: проверьте исходный код перед отправкой. Я не могу принимать ответы, которые работают медленнее, чем мой существующий код, естественно.

Изменить еще раз:

Я собрал список тестов на своей машине.

Ответ 1

Есть несколько небольших оптимизаций для вашей версии. Перевернув роли True и False, вы можете изменить "if flags[i] is False:" на "if flags[i]:". Начальное значение для второго оператора range может быть i*i вместо i*3. Ваша оригинальная версия занимает 0.166 секунды в моей системе. С этими изменениями версия ниже занимает 0.156 секунд в моей системе.

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5)
    flags = [True, True] + [False] * (limit - 2)
    # Step through all the odd numbers
    for i in range(3, limit, 2):
        if flags[i]:
            continue
        yield i
        # Exclude further multiples of the current prime number
        if i <= sub_limit:
            for j in range(i*i, limit, i<<1):
                flags[j] = True

Это не поможет вам решить проблему с памятью.

Перейдя в мир расширений C, я использовал версию разработки gmpy. (Отказ от ответственности: я один из разработчиков). Версия разработки называется gmpy2 и поддерживает изменяемые целые числа, называемые xmpz. Используя gmpy2 и следующий код, у меня есть время работы 0.140 секунд. Продолжительность работы для лимита в 1 000 000 000 составляет 158 секунд.

import gmpy2

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5)
    # Actual number is 2*bit_position + 1.
    oddnums = gmpy2.xmpz(1)
    current = 0
    while True:
        current += 1
        current = oddnums.bit_scan0(current)
        prime = 2 * current + 1
        if prime > limit:
            break
        yield prime
        # Exclude further multiples of the current prime number
        if prime <= sub_limit:
            for j in range(2*current*(current+1), limit>>1, prime):
                oddnums.bit_set(j)

Нажав оптимизацию и жертвуя ясностью, я получаю время работы от 0,107 до 123 секунд со следующим кодом:

import gmpy2

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5)
    # Actual number is 2*bit_position + 1.
    oddnums = gmpy2.xmpz(1)
    f_set = oddnums.bit_set
    f_scan0 = oddnums.bit_scan0
    current = 0
    while True:
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1
        if prime > limit:
            break
        yield prime
        # Exclude further multiples of the current prime number
        if prime <= sub_limit:
            list(map(f_set,range(2*current*(current+1), limit>>1, prime)))

Изменить: на основе этого упражнения я изменил gmpy2, чтобы принять xmpz.bit_set(iterator). Используя следующий код, время выполнения для всех простых чисел менее 1 000 000 000 составляет 56 секунд для Python 2.7 и 74 секунды для Python 3.2. (Как отмечено в комментариях, xrange быстрее, чем range.)

import gmpy2

try:
    range = xrange
except NameError:
    pass

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5)
    oddnums = gmpy2.xmpz(1)
    f_scan0 = oddnums.bit_scan0
    current = 0
    while True:
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1
        if prime > limit:
            break
        yield prime
        if prime <= sub_limit:
            oddnums.bit_set(iter(range(2*current*(current+1), limit>>1, prime)))

Редактировать # 2: Еще одна попытка! Я изменил gmpy2, чтобы принять xmpz.bit_set(slice). Используя следующий код, время запуска для всех простых чисел менее 1,000,000,000 составляет около 40 секунд для Python 2.7 и Python 3.2.

from __future__ import print_function
import time
import gmpy2

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    yield 2
    sub_limit = int(limit**0.5)
    flags = gmpy2.xmpz(1)
    # pre-allocate the total length
    flags.bit_set((limit>>1)+1)
    f_scan0 = flags.bit_scan0
    current = 0
    while True:
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1
        if prime > limit:
            break
        yield prime
        if prime <= sub_limit:
            flags.bit_set(slice(2*current*(current+1), limit>>1, prime))

start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)

Изменить №3: я обновил gmpy2, чтобы правильно поддерживать нарезку на уровне бит xmpz. Никаких изменений в производительности, но очень хороший API. Я немного подкорректировал, и у меня есть время примерно до 37 секунд. (См. Редактирование № 4 на изменения в gmpy2 2.0.0b1.)

from __future__ import print_function
import time
import gmpy2

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    sub_limit = int(limit**0.5)
    flags = gmpy2.xmpz(1)
    flags[(limit>>1)+1] = True
    f_scan0 = flags.bit_scan0
    current = 0
    prime = 2
    while prime <= sub_limit:
        yield prime
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1
        flags[2*current*(current+1):limit>>1:prime] = True
    while prime <= limit:
        yield prime
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1

start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)

Изменить # 4: Я внес некоторые изменения в gmpy2 2.0.0b1, которые нарушают предыдущий пример. gmpy2 больше не обрабатывает True как специальное значение, которое обеспечивает бесконечный источник 1-бит. -1 следует использовать вместо этого.

from __future__ import print_function
import time
import gmpy2

def prime_numbers(limit=1000000):
    '''Prime number generator. Yields the series
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
    using Sieve of Eratosthenes.
    '''
    sub_limit = int(limit**0.5)
    flags = gmpy2.xmpz(1)
    flags[(limit>>1)+1] = 1
    f_scan0 = flags.bit_scan0
    current = 0
    prime = 2
    while prime <= sub_limit:
        yield prime
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1
        flags[2*current*(current+1):limit>>1:prime] = -1
    while prime <= limit:
        yield prime
        current += 1
        current = f_scan0(current)
        prime = 2 * current + 1

start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)

Изменить # 5: Я сделал некоторые улучшения для gmpy2 2.0.0b2. Теперь вы можете перебирать все биты, которые либо установлены, либо очищены. Время работы улучшилось на ~ 30%.

from __future__ import print_function
import time
import gmpy2

def sieve(limit=1000000):
    '''Returns a generator that yields the prime numbers up to limit.'''

    # Increment by 1 to account for the fact that slices do not include
    # the last index value but we do want to include the last value for
    # calculating a list of primes.
    sieve_limit = gmpy2.isqrt(limit) + 1
    limit += 1

    # Mark bit positions 0 and 1 as not prime.
    bitmap = gmpy2.xmpz(3)

    # Process 2 separately. This allows us to use p+p for the step size
    # when sieving the remaining primes.
    bitmap[4 : limit : 2] = -1

    # Sieve the remaining primes.
    for p in bitmap.iter_clear(3, sieve_limit):
        bitmap[p*p : limit : p+p] = -1

    return bitmap.iter_clear(2, limit)

if __name__ == "__main__":
    start = time.time()
    result = list(sieve(1000000000))
    print(time.time() - start)
    print(len(result))

Ответ 2

Хорошо, так что это мой второй ответ, но поскольку скорость имеет смысл, я подумал, что должен был упомянуть bitarray модуль - даже если он bitstring nemesis:). Он идеально подходит для этого случая, поскольку он не только является расширением C (и, следовательно, быстрее, чем чистый Python имеет надежду на то), но также поддерживает назначение срезов. Это еще не доступно для Python 3.

Я даже не пытался оптимизировать это, я просто переписал версию bitstring. На моей машине я получаю 0,16 секунды для простых чисел менее миллиона.

За миллиард он работает отлично и завершается через 2 минуты 31 секунду.

import bitarray

def prime_bitarray(limit=1000000):
    yield 2
    flags = bitarray.bitarray(limit)
    flags.setall(False)
    sub_limit = int(limit**0.5)
    for i in xrange(3, limit, 2):
        if not flags[i]:
            yield i
            if i <= sub_limit:
                flags[3*i:limit:i*2] = True

Ответ 3

Хорошо, здесь (почти полный) всеобъемлющий бенчмаркинг, который я сделал сегодня вечером, чтобы узнать, какой код работает быстрее всего. Надеюсь, кто-то найдет этот список полезным. Я пропустил все, что занимает больше 30 секунд, чтобы завершить работу на моей машине.

Я хотел бы поблагодарить всех, кто внес вклад. Я получил много понимания от ваших усилий, и я надеюсь, что вы тоже.

Моя машина: AMD ZM-86, 2.40 Ghz Dual-Core, с 4 ГБ оперативной памяти. Это ноутбук HP Touchsmart Tx2. Обратите внимание, что хотя я, возможно, связался с пастебином, , я сравнивал все следующие на моей машине.

Я добавлю тест gmpy2, когда смогу его создать.

Все тесты тестируются в Python 2.6 x86

Возврат списка простых чисел n до 1 000 000: (с использованием Python генераторы)

версия генератора numbase Sebastian (обновлена) - 121 мс @

Mark Sieve + Wheel - 154 мс

версия Robert с разрезом - 159 мс

Моя улучшенная версия с нарезкой- 205 мс

Генератор Numpy с перечислением - 249 мс @

Mark Basic Sieve - 317 мс

улучшение casevh на моем оригинале решение - 343 мс

Модифицированное решение генератора numpy - 407 мс

Мой оригинальный метод в вопрос - 409 мс

Bitarray Solution - 414 мс @

Pure Python с bytearray - 1394 мс @

Скотта BitString решение - 6659 ms @

'@' означает, что этот способ способен генерировать до n < 1 000 000 000 моя установка машины.

Кроме того, если вам не нужны генератор и просто хотите весь список сразу:

решение numpy от RosettaCode - 32 мс @

(Решение numpy также способно генерировать до 1 миллиарда, что заняло 61,6259 секунд. Я подозреваю, что память была заменена один раз, следовательно, в два раза.)

Ответ 4

Связанный вопрос.
Самый быстрый способ перечислить все простые числа ниже N в python

Привет, я тоже ищу код в python для генерации простых чисел до 10 9, насколько это возможно, что сложно из-за проблемы с памятью. до сих пор я придумал это для генерации простых чисел до 10 6 и 10 * 7 (0,171s и 1,764s соответственно на моем старом компьютере), который, кажется, немного быстрее (по крайней мере, на моем компьютере), чем "Моя улучшенная версия с разрезом" из предыдущего сообщения, вероятно, потому, что я использую n//ii +1 (см. Ниже) вместо аналогичной len (флаги [i2:: я < 1]) в другом коде. пожалуйста, исправьте меня, если я ошибаюсь. Любые предложения по улучшению очень приветствуются.

def primes(n):
    """ Returns  a list of primes < n """
    sieve = [True] * n
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i]:
            sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
    return [2] + [i for i in xrange(3,n,2) if sieve[i]]

В одном из своих кодов Xavier использует флаги [i2:: я < 1 и len (flags [i2:: я < 1]), я вычислил размер явным, используя тот факт, что между ii.. n мы имеем (n-ii)//2 * я кратные 2 * i, так как мы хотим сосчитать ii, и мы суммируем 1 так, что len (sieve [ii:: 2 * i]) равно (ni * i)//( 2 * i) +1 Это сделает код быстрее. Базовый генератор, основанный на коде выше, будет:

def primesgen(n):
    """ Generates all primes <= n """
    sieve = [True] * n
    yield 2
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i]:
            yield i
            sieve[i*i::2*i] = [False]*((n-i*i-1)/(2*i)+1)
    for i in xrange(i+2,n,2):
        if sieve[i]: yield i

с небольшим изменением можно написать несколько более медленную версию вышеприведенного кода, которая начинается с сита половину размера сита = [True] * (n//2) и работает для того же n. не знаете, как это отражается в памяти. В качестве примера реализации измененная версия кода numset rosetta (возможно, быстрее), начиная с половины размера сита.

import numpy
def primesfrom3to(n):
    """ Returns a array of primes, 3 <= p < n """
    sieve = numpy.ones(n/2, dtype=numpy.bool)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]: sieve[i*i/2::i] = False
    return 2*numpy.nonzero(sieve)[0][1::]+1

Быстрее и больше генератора памяти:

import numpy
def primesgen1(n):
""" Input n>=6, Generates all primes < n """
sieve1 = numpy.ones(n/6+1, dtype=numpy.bool)
sieve5 = numpy.ones(n/6  , dtype=numpy.bool)
sieve1[0] = False
yield 2; yield 3;
for i in xrange(int(n**0.5)/6+1):
    if sieve1[i]:
        k=6*i+1; yield k;
        sieve1[ ((k*k)/6) ::k] = False
        sieve5[(k*k+4*k)/6::k] = False
    if sieve5[i]:
        k=6*i+5; yield k;
        sieve1[ ((k*k)/6) ::k] = False
        sieve5[(k*k+2*k)/6::k] = False
for i in xrange(i+1,n/6):
        if sieve1[i]: yield 6*i+1
        if sieve5[i]: yield 6*i+5
if n%6 > 1:
    if sieve1[i+1]: yield 6*(i+1)+1

или с еще большим количеством кода:

import numpy
def primesgen(n):
    """ Input n>=30, Generates all primes < n """
    size = n/30 + 1
    sieve01 = numpy.ones(size, dtype=numpy.bool)
    sieve07 = numpy.ones(size, dtype=numpy.bool)
    sieve11 = numpy.ones(size, dtype=numpy.bool)
    sieve13 = numpy.ones(size, dtype=numpy.bool)
    sieve17 = numpy.ones(size, dtype=numpy.bool)
    sieve19 = numpy.ones(size, dtype=numpy.bool)
    sieve23 = numpy.ones(size, dtype=numpy.bool)
    sieve29 = numpy.ones(size, dtype=numpy.bool)
    sieve01[0] = False
    yield 2; yield 3; yield 5;
    for i in xrange(int(n**0.5)/30+1):
        if sieve01[i]:
            k=30*i+1; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+10*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+16*k)/30::k] = False
            sieve19[(k*k+18*k)/30::k] = False
            sieve23[(k*k+22*k)/30::k] = False
            sieve29[(k*k+28*k)/30::k] = False
        if sieve07[i]:
            k=30*i+7; yield k;
            sieve01[(k*k+ 6*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+16*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+ 4*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+22*k)/30::k] = False
            sieve29[(k*k+10*k)/30::k] = False
        if sieve11[i]:
            k=30*i+11; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+20*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+26*k)/30::k] = False
            sieve19[(k*k+18*k)/30::k] = False
            sieve23[(k*k+ 2*k)/30::k] = False
            sieve29[(k*k+ 8*k)/30::k] = False
        if sieve13[i]:
            k=30*i+13; yield k;
            sieve01[(k*k+24*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+ 4*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+16*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+28*k)/30::k] = False
            sieve29[(k*k+10*k)/30::k] = False
        if sieve17[i]:
            k=30*i+17; yield k;
            sieve01[(k*k+ 6*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+26*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+14*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+ 2*k)/30::k] = False
            sieve29[(k*k+20*k)/30::k] = False
        if sieve19[i]:
            k=30*i+19; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+10*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+ 4*k)/30::k] = False
            sieve19[(k*k+12*k)/30::k] = False
            sieve23[(k*k+28*k)/30::k] = False
            sieve29[(k*k+22*k)/30::k] = False
        if sieve23[i]:
            k=30*i+23; yield k;
            sieve01[(k*k+24*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+14*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+26*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+ 8*k)/30::k] = False
            sieve29[(k*k+20*k)/30::k] = False
        if sieve29[i]:
            k=30*i+29; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+20*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+14*k)/30::k] = False
            sieve19[(k*k+12*k)/30::k] = False
            sieve23[(k*k+ 8*k)/30::k] = False
            sieve29[(k*k+ 2*k)/30::k] = False
    for i in xrange(i+1,n/30):
            if sieve01[i]: yield 30*i+1
            if sieve07[i]: yield 30*i+7
            if sieve11[i]: yield 30*i+11
            if sieve13[i]: yield 30*i+13
            if sieve17[i]: yield 30*i+17
            if sieve19[i]: yield 30*i+19
            if sieve23[i]: yield 30*i+23
            if sieve29[i]: yield 30*i+29
    i += 1
    mod30 = n%30
    if mod30 > 1:
        if sieve01[i]: yield 30*i+1
    if mod30 > 7:
        if sieve07[i]: yield 30*i+7
    if mod30 > 11:
        if sieve11[i]: yield 30*i+11
    if mod30 > 13:
        if sieve13[i]: yield 30*i+13
    if mod30 > 17:
        if sieve17[i]: yield 30*i+17
    if mod30 > 19:
        if sieve19[i]: yield 30*i+19
    if mod30 > 23:
        if sieve23[i]: yield 30*i+23

Ps: Если у вас есть какие-то предложения о том, как ускорить этот код, что-либо от порядка операций операций до предварительного вычисления чего-либо, прокомментируйте.

Ответ 5

Здесь версия, которую я написал некоторое время назад; это может быть интересно сравнить с вашим для скорости. Однако он ничего не делает о космических проблемах (на самом деле они, вероятно, хуже, чем с вашей версией).

from math import sqrt

def basicSieve(n):
    """Given a positive integer n, generate the primes < n."""
    s = [1]*n
    for p in xrange(2, 1+int(sqrt(n-1))):
        if s[p]:
            a = p*p
            s[a::p] = [0] * -((a-n)//p)
    for p in xrange(2, n):
        if s[p]:
            yield p 

У меня есть более быстрые версии, используя колесо, но они намного сложнее. Исходный источник здесь.

Хорошо, здесь версия с использованием колеса. wheelSieve - основная точка входа.

from math import sqrt
from bisect import bisect_left

def basicSieve(n):
    """Given a positive integer n, generate the primes < n."""
    s = [1]*n
    for p in xrange(2, 1+int(sqrt(n-1))):
        if s[p]:
            a = p*p
            s[a::p] = [0] * -((a-n)//p)
    for p in xrange(2, n):
        if s[p]:
            yield p

class Wheel(object):
    """Class representing a wheel.

    Attributes:
       primelimit -> wheel covers primes < primelimit.
       For example, given a primelimit of 6
       the wheel primes are 2, 3, and 5.
       primes -> list of primes less than primelimit
       size -> product of the primes in primes;  the modulus of the wheel
       units -> list of units modulo size
       phi -> number of units

    """
    def __init__(self, primelimit):
        self.primelimit = primelimit
        self.primes = list(basicSieve(primelimit))

        # compute the size of the wheel
        size = 1
        for p in self.primes:
            size *= p
        self.size = size

        # find the units by sieving
        units = [1] * self.size
        for p in self.primes:
            units[::p] = [0]*(self.size//p)
        self.units = [i for i in xrange(self.size) if units[i]]

        # number of units
        self.phi = len(self.units)

    def to_index(self, n):
        """Compute alpha(n), where alpha is an order preserving map
        from the set of units modulo self.size to the nonnegative integers.

        If n is not a unit, the index of the first unit greater than n
        is given."""
        return bisect_left(self.units, n % self.size) + n // self.size * self.phi

    def from_index(self, i):
        """Inverse of to_index."""

        return self.units[i % self.phi] + i // self.phi * self.size

def wheelSieveInner(n, wheel):
    """Given a positive integer n and a wheel, find the wheel indices of
    all primes that are less than n, and that are units modulo the
    wheel modulus.
    """

    # renaming to avoid the overhead of attribute lookups
    U = wheel.units
    wS = wheel.size
    # inverse of unit map
    UI = dict((u, i) for i, u in enumerate(U))
    nU = len(wheel.units)

    sqroot = 1+int(sqrt(n-1)) # ceiling of square root of n

    # corresponding index (index of next unit up)
    sqrti = bisect_left(U, sqroot % wS) + sqroot//wS*nU
    upper = bisect_left(U, n % wS) + n//wS*nU
    ind2 = bisect_left(U, 2 % wS) + 2//wS*nU

    s = [1]*upper
    for i in xrange(ind2, sqrti):
        if s[i]:
            q = i//nU
            u = U[i%nU]
            p = q*wS+u
            u2 = u*u
            aq, au = (p+u)*q+u2//wS, u2%wS
            wp = p * nU
            for v in U:
                # eliminate entries corresponding to integers
                # congruent to p*v modulo p*wS
                uvr = u*v%wS
                m = aq + (au > uvr)
                bot = (m + (q*v + u*v//wS - m) % p) * nU + UI[uvr]
                s[bot::wp] = [0]*-((bot-upper)//wp)
    return s

def wheelSieve(n, wheel=Wheel(10)):
    """Given a positive integer n, generate the list of primes <= n."""
    n += 1
    wS = wheel.size
    U = wheel.units
    nU = len(wheel.units)
    s = wheelSieveInner(n, wheel)
    return (wheel.primes[:bisect_left(wheel.primes, n)] +
            [p//nU*wS + U[p%nU] for p in xrange(bisect_left(U, 2 % wS)
             + 2//wS*nU, len(s)) if s[p]])

Что касается колеса: хорошо, вы знаете, что (кроме 2) все простые числа странны, поэтому большинство сит пропускают все четные числа. Аналогично, вы можете пойти немного дальше и заметить, что все простые числа (кроме 2 и 3) соответствуют 1 или 5 по модулю 6 (== 2 * 3), поэтому вы можете уйти с сохранением записей для этих чисел в вашем сите, Следующий шаг - отметить, что все простые числа (кроме 2, 3 и 5) соответствуют одному из 1, 7, 11, 13, 17, 19, 23, 29 (по модулю 30) (здесь 30 == 2 * 3 * 5) и т.д.

Ответ 6

Одним из улучшений скорости, которые вы можете сделать с помощью bitstring, является использование метода set, когда вы исключаете кратные текущего номера.

Итак, жизненный раздел становится

for i in range(3, limit, 2):
    if flags[i]:
        yield i
        if i <= sub_limit:
            flags.set(1, range(i*3, limit, i*2))    

На моей машине это выполняется примерно в 3 раза быстрее, чем оригинал.

Моя другая мысль заключалась в том, чтобы использовать bitstring для представления только нечетных чисел. Затем вы можете найти несохраненные биты, используя flags.findall([0]), который возвращает генератор. Не уверен, что это будет намного быстрее (найти битовые шаблоны не так просто сделать эффективно).

[Полное раскрытие: я написал модуль битовой строки, так что у меня есть гордость здесь!]


В качестве сравнения я также взял кишки из метода bitstring, чтобы он делал это точно так же, но без проверки диапазона и т.д. Я думаю, что это дает разумный нижний предел для чистого Python, который работает на миллиард элементы (без изменения алгоритма, который я считаю обманом!)

def prime_pure(limit=1000000):
    yield 2
    flags = bytearray((limit + 7) // 8)
    sub_limit = int(limit**0.5)
    for i in xrange(3, limit, 2):
        byte, bit = divmod(i, 8)
        if not flags[byte] & (128 >> bit):
            yield i
            if i <= sub_limit:
                for j in xrange(i*3, limit, i*2):
                    byte, bit = divmod(j, 8)
                    flags[byte] |= (128 >> bit)

На моей машине это составляет около 0.62 секунды для миллиона элементов, что означает, что это примерно четверть скорости ответа битаррей. Я считаю это вполне разумным для чистого Python.

Ответ 7

Целочисленные типы Python могут быть произвольного размера, поэтому вам не нужна умная библиотека битовой строки для представления набора бит, всего лишь одно число.

Вот код. Он легко справляется с лимитом в 1 000 000 человек и может даже обрабатывать 10 000 000, не жалуясь слишком много:

def multiples_of(n, step, limit):
    bits = 1 << n
    old_bits = bits
    max = 1 << limit
    while old_bits < max:
        old_bits = bits
        bits += bits << step
        step *= 2
    return old_bits

def prime_numbers(limit=10000000):
    '''Prime number generator. Yields the series                                                                        
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...                                                                              
    using Sieve of Eratosthenes.                                                                                        
    '''
    yield 2
    sub_limit = int(limit**0.5)
    flags = ((1 << (limit - 2)) - 1) << 2
    # Step through all the odd numbers                                                                                  
    for i in xrange(3, limit, 2):
        if not (flags & (1 << i)):
            continue
        yield i
        # Exclude further multiples of the current prime number                                                         
        if i <= sub_limit:
            flags &= ~multiples_of(i * 3, i << 1, limit)

Функция multiples_of позволяет избежать затрат на установку каждого отдельного набора по отдельности. Он устанавливает начальный бит, сдвигает его на начальный шаг (i << 1) и добавляет результат сам по себе. Затем он удваивает шаг, так что следующий сдвиг копирует оба бита и так далее, пока не достигнет предела. Это устанавливает все кратные числа до предела в O (log (limit)).

Ответ 8

Один из вариантов, который вы, возможно, захотите посмотреть, - это просто компилировать модуль C/С++, чтобы вы имели прямой доступ к функциям бит-twiddling. AFAIK вы могли бы написать что-то такого характера и только возвращать результаты по завершении вычислений, выполненных на C/С++. Теперь, когда я набираю это, вы также можете посмотреть на numpy, который хранит массивы как родные C для скорости. Я не знаю, будет ли это быстрее, чем модуль bitstring!

Ответ 9

Другой действительно глупый вариант, но это может помочь, если вам действительно нужен большой список простых чисел, доступных очень быстро. Скажем, если вы нуждаетесь в них в качестве инструмента для ответа на проблемы проекта Эйлера (если проблема в том, что просто оптимизировать ваш код как игру ума, это не имеет значения).

Используйте любое медленное решение для создания списка и сохраните его в исходном файле python, говорит primes.py, который просто содержит:

primes = [ a list of a million primes numbers here ]

Теперь, чтобы использовать их, вам просто нужно сделать import primes, и вы получите их с минимальным размером памяти на скорости дискового ввода-вывода. Сложность - количество простых чисел: -)

Даже если вы использовали плохо оптимизированное решение для создания этого списка, это будет сделано только один раз, и это не имеет большого значения.

Возможно, вы можете сделать это еще быстрее, используя pickle/unpickle, но вы получите идею...

Ответ 10

Вы можете использовать сегментированное сито Эратосфена. Память, используемая для каждого сегмента, повторно используется для следующего сегмента.

Ответ 11

Вот некоторый код Python3, который использует меньше памяти, чем решения bitarray/bitstring, опубликованные на сегодняшний день, и около 1/8 памяти Роберта Уильяма Хэнкса primesgen(), при этом работает быстрее, чем primesgen() (немного быстрее на 1,000,000, используя 37 Кбайт памяти, в 3 раза быстрее, чем primesgen() на 1,000,000,000, используя до 34 МБ). Увеличение размера блока (переменная часть кода) использует больше памяти, но ускоряет работу программы, до предела - я выбрал значение, чтобы его вклад в память составлял около 10% от решетки n//30 байт. Это не так эффективно для памяти, как бесконечный генератор Will Ness (fooobar.com/info/9543/...), потому что он записывает (и возвращает в конце в сжатой форме) все рассчитанные простые числа.

Это должно работать корректно, если вычисление квадратного корня получается точным (около 2 ** 51, если Python использует 64-битные удвоения). Однако вы не должны использовать эту программу для поиска больших чисел!

(Я не пересчитывал смещения, просто взял их из кода Роберта Уильяма Хэнкса. Спасибо, Роберт!)

import numpy as np
def prime_30_gen(n):
    """ Input n, int-like.  Yields all primes < n as Python ints"""
    wheel = np.array([2,3,5])
    yield from wheel[wheel < n].tolist()
    powers = 1 << np.arange(8, dtype='u1')
    odds = np.array([1, 7, 11, 13, 17, 19, 23, 29], dtype='i8')
    offsets=np.array([[0,6,10,12,16,18,22,28],[6,24,16,12,4,0,22,10],
                      [0,6,20,12,26,18,2,8],  [24,6,4,18,16,0,28,10],
                      [6,24,26,12,14,0,2,20], [0,24,10,18,4,12,28,22],
                      [24,6,14,18,26,0,8,20], [0,24,20,18,14,12,8,2]],
                     dtype='i8')
    offsets = {d:f for d,f in zip(odds, offsets)}
    sieve = 255 * np.ones((n + 28) // 30, dtype='u1')
    if n % 30 > 1: sieve[-1] >>= 8 - odds.searchsorted(n % 30)
    sieve[0] &= ~1 
    for i in range((int((n - 1)**0.5) + 29) // 30):
        for odd in odds[(sieve[i] & powers).nonzero()[0]]:
            k = i * 30 + odd
            yield int(k)
            for clear_bit, off in zip(~powers, offsets[odd]): 
                sieve[(k * (k + off)) // 30 :: k] &= clear_bit
    chunk = min(1 + (n >> 13), 1<<15)
    for i in range(i + 1, len(sieve), chunk):
        a = (sieve[i : i + chunk, np.newaxis] & powers)
        a = np.flatnonzero(a.astype('bool')) + i * 8
        a = (a // 8 * 30 + odds[a & 7]).tolist()
        yield from a
    return sieve

Примечание: если вам нужна реальная скорость и требуется 2 ГБ оперативной памяти для списка простых чисел до 10 ** 9, вы должны использовать pyprimesieve (на https://pypi.python.org/, используя primesieve http://primesieve.org/).