Это генератор псевдослучайных чисел Парка-Миллера:
def gen1(a=783):
while True:
a = (a * 48271) % 0x7fffffff
yield a
783
— это просто произвольное семя. 48271
— это коэффициент, рекомендованный Парком и Миллером в оригинальной статье (PDF: Парк, Стивен К.; Миллер, Кит В. (1988). Генераторы случайных чисел: трудно найти хорошие)
Я хотел бы улучшить производительность этого LCG. В литературе описан способ избежать деления с помощью побитовых приемов (источник):
Простой модуль требует вычисления произведения двойной ширины и явного шага редукции. Если используется модуль чуть меньше степени 2 (популярны простые числа Мерсенна 231−1 и 261−1, а также 232< /sup>−5 и 264−59), сокращение по модулю m = 2e − d может быть реализовано дешевле, чем обычное деление двойной ширины с использованием тождества 2 e ≡ d (mod m).
Отметив, что модуль 0x7fffffff
на самом деле является простым числом Мерсенна 2 ** 32 - 1, вот идея, реализованная в Python:
def gen2(a=783):
while True:
a *= 48271
a = (a & 0x7fffffff) + (a >> 31)
a = (a & 0x7fffffff) + (a >> 31)
yield a
Базовый тестовый скрипт:
import time, sys
g1 = gen1()
g2 = gen2()
for g in g1, g2:
t0 = time.perf_counter()
for i in range(int(sys.argv[1])): next(g)
print(g.__name__, time.perf_counter() - t0)
Производительность улучшена в pypy (7.3.0 @ 3.6.9), например, генерируется 100 млн терминов:
$ pypy lcg.py 100000000
gen1 0.4366550260456279
gen2 0.3180829349439591
К сожалению, в CPython (3.9.0/Linux) производительность фактически ухудшилась:
$ python3 lcg.py 100000000
gen1 20.650125587941147
gen2 26.844335232977755
Мои вопросы:
- Почему побитовая арифметика, обычно рекламируемая как оптимизация, на самом деле даже медленнее, чем операция по модулю в CPython?
- Можете ли вы улучшить производительность этого PRNG под CPython каким-либо другим способом, возможно, используя numpy или типы?
Обратите внимание, что здесь не обязательно требуются целые числа произвольной точности, потому что этот генератор никогда не будет давать числа длиннее, чем:
>>> 0x7fffffff.bit_length()
31
numpy
сnumba
, возможно, или просто создание C-расширения (возможно, с Cython). - person juanpa.arrivillaga   schedule 27.01.2021from random import getrandbits
.%timeit getrandbits(31)
IPython было 50 нс, а вашеgen1()
было 210 нс. Я построил C-версию gen1() и gen2() и вызвал ctypes, но gen1() работал с той же скоростью, а gen2() все еще медленнее. - person Mark Tolonen   schedule 27.01.2021