Ускорить битовые / битовые операции в Python? - PullRequest
39 голосов
/ 24 мая 2010

Я написал генератор простых чисел, используя Сито Эратосфена и 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) действительно неосуществимо,поэтому я посмотрел на цепочку бит .Я полагал, что использование 1 бита для каждого флага будет намного более эффективным с точки зрения памяти.Тем не менее, производительность программы резко упала - 24 секунды времени выполнения, для простого числа до 1 000 000.Вероятно, это связано с внутренней реализацией цепочки битов.

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

У меня такой вопрос, Есть ли способ ускорить мою программу с использованием цепочки битов или без нее?

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

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

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

Ответы [ 11 ]

34 голосов
/ 25 мая 2010

Есть несколько небольших оптимизаций для вашей версии. Изменив роли Истинного и Ложного, вы можете изменить «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)

Edit # 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))
8 голосов
/ 25 мая 2010

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

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

Моя машина: AMD ZM-86, 2,40 ГГц, двухъядерный, с 4 ГБ оперативной памяти. Это ноутбук HP Touchsmart Tx2. Обратите внимание, что, хотя я мог связываться с pastebin, я проверил все следующее на своей машине.

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

Все тесты протестированы в Python 2.6 x86

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

версия генератора пустышки Себастьяна (обновлено) - 121 мс @

Сито Марка + Колесо - 154 мс

версия Роберта с нарезкой - 159 мс

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

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

Основное сито Марка - 317 мс

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

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

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

Bitarray Solution - 414 мс @

Pure Python с байтовым массивом - 1394 мс @

Решение Скотта BitString - 6659 мс @

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

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

NumPy Solution от RosettaCode - 32 мс @

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

8 голосов
/ 25 мая 2010

ОК, так что это мой второй ответ, но так как скорость важна, я подумал, что должен упомянуть модуль bitarray - даже если это заклятый враг bitstring : ). Он идеально подходит для этого случая, поскольку он не только является расширением 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
6 голосов
/ 26 мая 2010

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

Привет, я слишком ищу код на Python для генерации простых чисел до 10 ^ 9 так быстро, как я могу, что сложно из-за проблемы с памятью. до сих пор я придумал это для генерации простых чисел до 10 ^ 6 & 10 ^ 7 (тактирование 0,171 с и 1764 с соответственно на моей старой машине), что, кажется, немного быстрее (по крайней мере, на моем компьютере), чем «Моя улучшенная версия с нарезкой» из предыдущего поста, вероятно, потому что я использую n//i-i +1 (см. ниже) вместо аналогичного len(flags[i2::i<<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]]

В одном из своих кодов Ксавье использует flags[i2::i<<1] и len(flags[i2::i<<1]). Я вычислил размер явно, используя тот факт, что между i*i..n мы имеем (n-i*i)//2*i, кратные 2*i, потому что мы хотим подсчитать i*i, и мы суммируем 1, поэтому len(sieve[i*i::2*i]) равно (n-i*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

с небольшой модификацией можно написать немного более медленную версию приведенного выше кода, которая начинается с сита, половина размера которого sieve = [True] * (n//2) и работает для того же n. не уверен, как это отразится на проблеме с памятью. В качестве примера реализации здесь приведен модифицированная версия кода NumPy 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: Если у вас есть какие-либо предложения о том, как ускорить этот код, что-нибудь от изменения порядка операций до предварительного вычисления чего-либо, пожалуйста, прокомментируйте.

4 голосов
/ 24 мая 2010

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

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) и т. Д.

3 голосов
/ 24 мая 2010

Одним из улучшений скорости, которое вы можете сделать с помощью цепочки битов, является использование метода '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 раза быстрее, чем оригинал.

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

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


В качестве сравнения я также вытащил кишки из метода цепочек битов, чтобы он делал это таким же образом, но без проверки диапазона и т. Д. Я думаю, что это дает разумный нижний предел для чистого 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 секунды для миллиона элементов, что означает примерно четверть скорости ответа bitarray. Я думаю, что это вполне разумно для чистого Python.

2 голосов
/ 24 мая 2010

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

2 голосов
/ 24 мая 2010

Целочисленные типы 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)).

1 голос
/ 30 октября 2015

Вот код Python3, который использует меньше памяти, чем решения на основе битовых массивов / цепочек битов, опубликованные на сегодняшний день, и примерно на 1/8 памяти primesgen () Роберта Уильяма Хэнкса, в то же время работает быстрее, чем primesgen () (незначительно быстрее на 1 000 000, используя37 КБ памяти, в 3 раза быстрее, чем primesgen () при 1 000 000 000 при использовании до 34 МБ).Увеличение размера чанка (переменная чанка в коде) использует больше памяти, но ускоряет программу до предела - я выбрал значение так, чтобы его вклад в память составлял менее 10% от n // 30 байт сита.Это не так эффективно для памяти, как Бесконечный генератор Усса (см. Также https://stackoverflow.com/a/19391111/5439078), потому что он записывает (и возвращает в конце в сжатой форме) все вычисленные простые числа.

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

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

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/).

1 голос
/ 02 февраля 2012

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

...