在 python 中舍入随机数的最快方法

Fastest way to round random numbers in python

我想生成达到一定精度的随机数,一次一个(所以我不是在寻找矢量化解决方案)。

我在 Whosebug 的 中找到了一个方法,它按照承诺给了我这些基准测试。该方法绝对快了将近两倍。 现在,这就是让我困惑的地方。

%timeit int(0.5192853551955484*(10**5)+0.5)/(10.**5)      #> 149 ns ± 5.76 ns per loop 
%timeit round(0.5192853551955484, 5)                      #> 432 ns ± 11.7 ns per loop
## Faster as expected

fl = random.random()
pr = 5
%timeit int(fl*(10**pr)+0.5)/(10.**pr)                    #> 613 ns ± 27.9 ns per loop 
%timeit round(fl, pr)                                     #> 444 ns ± 9.25 ns per loop
## Slower?!

%timeit int(random.random()*(10**5)+0.5)/(10.**5)         #> 280 ns ± 29.3 ns per loop 
%timeit round(random.random(), 5)                         #> 538 ns ± 17.5 ns per loop
## Faster than using a variable even though it has the overhead
## of creating a random number for each call?

为什么在使用变量时上述方法变慢了?当我直接传递随机生成的数字时,它会恢复丢失的速度。 我错过了什么?

在我的代码中,因为我需要在多个地方进行舍入,所以我希望将它包装在一个函数中,如下所示。我知道与函数调用相关的成本很小,但我不认为这是花费大部分时间的原因。 说,它仍然应该比 python 的标准 round() 函数更快。

def rounder(fl, pr):
    p = float(10**pr)
    return int(fl * p + 0.5)/p

%timeit rounder(random.random(), 5)                       #> 707 ns ± 14.2 ns per loop 
%timeit round(random.random(), 5)                         #> 525 ns ± 22.1 ns per loop

## Having a global variable does make it faster by not having to do the 10**5 everytime
p = float(10**5)
def my_round_5(fl):
    return int(fl* p + 0.5)/p

%timeit my_round_5(random.random())                       #> 369 ns ± 18.9 ns per loop

我宁愿在我的代码中没有全局变量,但假设我承认这个要求,但性能增益比使用没有变量的公式要小。

所以,最后一个问题是,哪种方法对我使用最有益? 切换到需要全局变量的函数只有 100-150ns 的增益。或者有什么办法可以更快。

Python 字节编译器“知道”数字是如何工作的,并且它使用这些知识来尽可能地优化事物。您可以使用 dis 模块查看发生了什么。

例如,您的第一个“快速”示例:

from dis import dis

def fn():
    return int(0.5192853551955484*(10**5)+0.5)/(10.**5)

dis(fn)

实际上是:

  2           0 LOAD_GLOBAL              0 (int)
              2 LOAD_CONST               1 (51929.03551955484)
              4 CALL_FUNCTION            1
              6 LOAD_CONST               2 (100000.0)
              8 BINARY_TRUE_DIVIDE
             10 RETURN_VALUE

即它知道 0.5192853551955484*(10**5)+0.5 的计算结果并在编译字节码时执行它。如果你有 pr 作为参数,它不能这样做,所以在 运行 代码时必须做更多的工作。

要回答“什么是最好的”问题,可能是这样的:

def fn(pr):
     # cache to prevent global lookup
     rng = random.random
     # evaluate precision once
     p = 10. ** pr
     # generate infinite stream of random numbers
     while True:
         yield int(rng() * p + 0.5) / p

可以作为基准:

x = fn(5)
%timeit next(x)

每个循环给出 ~160ns,而:

%timeit int(fl*(10**pr)+0.5)/(10**pr)
%timeit int(fl*(10.**pr)+0.5)/(10.**pr)

运行 在 ~500 和 ~250ns 内。没有意识到 int 与 float 的幂运算如此不同!