为什么"正常"生成随机整数的方法如此之慢?

时间:2016-05-31 04:31:32

标签: python performance random

不是数学专业或cs专业,我只是愚弄python(通常为视频游戏制作模拟/理论制作的脚本),我发现了random.randint有多么糟糕的表现。它让我想知道为什么random.randint或random.randrange按原样使用/制作。我创建了一个函数,用于生成(用于所有意图和实际目的)与random.randint相同的结果:

big_bleeping_float= (2**64 - 2)/(2**64 - 2)
def fastrandint(start, stop):
  return start + int(random.random() * (stop - start + big_bleeping_float))

使用它可以产生180%的速度提升,以生成0-65范围内的整数(与random.randrange(0,66)相比,这是下一个最快的方法。

>>> timeit.timeit('random.randint(0, 66)', setup='from numpy import random', number=10000)
0.03165552873121058

>>> timeit.timeit('random.randint(0, 65)', setup='import random', number=10000)
0.022374771118336412

>>> timeit.timeit('random.randrange(0, 66)', setup='import random', number=10000)
0.01937231027605435

>>> timeit.timeit('fastrandint(0, 65)', setup='import random; from fasterthanrandomrandom import fastrandint', number=10000)
0.0067909916844523755

此外,这个函数作为random.choice的替代品的改编速度提高了75%,我确信添加大于一个步进范围会更快(尽管我没有测试过)。使用fastrandint函数几乎可以提高速度的两倍,您可以简单地将其写入内联:

>>> timeit.timeit('int(random.random() * (65 + big_bleeping_float))', setup='import random; big_bleeping_float= (2**64 - 2)/(2**64 - 2)', number=10000)
0.0037642723021917845

总而言之,为什么我错了我的功能更好,为什么它更好更快,并且还有更快的方式去做我正在做的事情?

3 个答案:

答案 0 :(得分:2)

random.randint()和其他人正在调用random.getrandbits()这可能效率低于直接调用random(),但有充分理由。

使用调用randint的{​​{1}}实际上更为正确,因为它可以无偏见的方式完成。

您可以看到,使用random.random生成某个范围内的值最终会被偏置,因为只有M个浮点值介于0和1之间(对于M非常大)。取一个不分为M的N,然后如果我们为random.getrandbits()写M = k N + r。充其量,使用0<r<N 我们会得到random.random() * (N+1)个数字,其中概率(k + 1)/ M和r数字以概率N-r出现。 (这是充其量,使用鸽子洞原则 - 在实践中我预计偏见会更糟糕。)

请注意,这种偏见仅适用于

  • 大量抽样
  • 其中N是M的大部分,(0,1)
  • 中的浮点数

所以它可能对你不重要,除非你知道你需要无偏见的价值观 - 比如科学计算等。

相反,k/M的值可以通过使用重复调用randint(0,N)的拒绝采样来无偏。当然,管理这会带来额外的开销。

<强>除了

如果你最终使用自定义随机实现,那么

来自python 3 docs

  

几乎所有模块函数都依赖于random()的基本函数   在半开放范围[0.0,1.0]内均匀生成随机浮点数。

这表明random.getrandbits()和其他人可以使用randint来实施。如果是这种情况我会期望它们变慢, 每次调用至少产生一个加法函数调用开销。

查看https://stackoverflow.com/a/37540577/221955中引用的代码,您可以看到如果随机实现没有提供random.random函数,就会发生这种情况。

答案 1 :(得分:2)

randint调用randrange执行一系列范围/类型检查和转换,然后使用_randbelow生成随机int。 _randbelow再次进行一些范围检查,最后使用random

因此,如果您删除所有边缘案例检查和一些函数调用开销,那么fastrandint更快就会出乎意料。

答案 2 :(得分:0)

这可能很少出现问题,但randint(0,10**1000)fastrandint(0,10**1000)崩溃时有效。较慢的时间可能是您需要支付的价格,以便拥有适用于所有可能情况的功能......