Медленная оптимизация поиска Python Prime

У меня есть 2 версии кода. Оба ищут все простые числа ниже определенного потолочного значения.

Первый — это проверка по набору всех нечетных чисел. Второй в тестировании против набора всех 6к-1 и 6к + 1.

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

Для менее 1 миллиона человек первый работает за 1,477835 секунды, а второй — за 1,521462 секунды.

Для менее чем 10 миллионов человек первый работает в течение 30,929565 секунд, а второй — 32,825142 секунды.

Я не тестировал их на другой машине.

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

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

Может быть, из-за того, что у меня больше вложенных циклов? Или дело в доступе к памяти? Может быть, это причуда питона, когда важны детали реализации? Может быть, в каком-то смысле первая версия проще и, следовательно, может быть лучше оптимизирована с помощью python по сравнению со второй?

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

#!/usr/bin/env python3.8

import sys
import time
import math

if len(sys.argv) != 2:
    print('usage:')
    print('    (ceiling)')
    print('        Find primes less than ceiling.')
    sys.exit(-1)

try:
    ceiling = int(sys.argv[1])
except:
    print('Fail to parse ceiling ({}) as integer.'.format(sys.argv[1]))
    sys.exit(-2)

if ceiling < 3:
    print('Minimum ceiling value is 3.')
    sys.exit(-3)

prime = [3]

# start benchmark
bench_start = time.perf_counter()

# range(start, end, step) does not include end
# step = 2 to avoid checking even numbers
for i in range(5, ceiling + 1, 2):
    # test only against factors less than the square root
    root = math.sqrt(i)
    for j in prime:
        if j > root:
            prime.append(i)
            break
        if i % j == 0:
            break

# end of benchmark
bench_end = time.perf_counter()

# prepend 2 to the prime list since it was skipped
prime.insert(0, 2)

print(prime)
print('Number of, primes less than {0}, found: {1}'.format(ceiling, len(prime)))

# benchmark report
print('Time elapsed: {:f}s'.format(bench_end - bench_start))

sys.exit(0)

#!/usr/bin/env python3.8

import sys
import time
import math

if len(sys.argv) != 2:
    print('usage:')
    print('    (ceiling)')
    print('        Find primes less than or equal to ceiling.')
    sys.exit(-1)

try:
    ceiling = int(sys.argv[1])
except:
    print('Fail to parse ceiling ({}) as integer.'.format(sys.argv[1]))
    sys.exit(-2)

if ceiling < 3:
    print('Minimum ceiling value is 3.')
    sys.exit(-3)

prime = [3]

# start benchmark
bench_start = time.perf_counter()

# range(start, end, step) does not include end
# test only for 6k-1 and 6k+1
# use (ceiling+1)+1 in case of ceiling = 6k-1
for h in range(6, ceiling+2, 6):
    for i in [h-1, h+1]:
        # test only against factors less than the square root
        root = math.sqrt(i)
        for j in prime:
            if j > root:
                prime.append(i)
                break
            if i % j == 0:
                break

# end of benchmark
bench_end = time.perf_counter()

# when ceiling = {6k-1, 6k}, remove 6k+1
if prime[-1] > ceiling:
    prime.pop()

# prepend 2 to the prime list since it was skipped
prime.insert(0, 2)

print(prime)
print('Number of, primes less than or equal to {0}, found: {1}'.format(ceiling, len(prime)))

# benchmark report
print('Time elapsed: {:f}s'.format(bench_end-bench_start))

sys.exit(0)
```

1 ответ
1

Разница в среднем составляет 0,2 микросекунды на каждое проверяемое число.

Чтобы убедиться, что это внутренний цикл, попробуйте развернуть его во второй программе:

...
for i in range(5, ceiling+1, 6):
    # test only against factors less than the square root
    # just use the sqrt of the bigger number to avoid another call to sqrt
    root = math.sqrt(i + 2)

    # tests 6k - 1
    for j in prime:
        if j > root:
            prime.append(i)
            break
        if i % j == 0:
            break

    # tests 6k + 1
    i += 2
    for j in prime:
        if j > root:
            prime.append(i)
            break
        if i % j == 0:
            break
    ...

  • Я попробовал размотать внутренний цикл. Хотя он и сделал его быстрее, он все же был медленнее, чем первая версия. Однако за счет извлечения корневого вычисления из внутреннего цикла и вычисления только для большего числа, как вы и предполагали, производительность значительно повысилась по сравнению с первой версией. Итак, в заключение, вычисление квадратного корня, по-видимому, имеет более важное значение для производительности.

    — Десмонд Роудс

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *