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

Rit*_*wik 0 python rounding python-3.x python-3.6

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

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

%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?
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

Sam*_*son 5

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

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

from dis import dis

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

dis(fn)
Run Code Online (Sandbox Code Playgroud)

实际上是:

from dis import dis

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

dis(fn)
Run Code Online (Sandbox Code Playgroud)

即它知道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
Run Code Online (Sandbox Code Playgroud)

可以通过以下方式进行基准测试:

x = fn(5)
%timeit next(x)
Run Code Online (Sandbox Code Playgroud)

每个循环给 ~160ns,同时:

%timeit int(fl*(10**pr)+0.5)/(10**pr)
%timeit int(fl*(10.**pr)+0.5)/(10.**pr)
Run Code Online (Sandbox Code Playgroud)

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