python中randint函数的用法,randint函数Python

  python中randint函数的用法,randint函数Python

  本文主要介绍了python中randint函数的效率缺陷,讨论了random模块的实现,并讨论了一些更快速的生成伪随机整数的替代方法,有需要的人可以参考。

  00-1010 1.前言2。测试randint () 3的运行效率。从源代码random.random () Random () 4分析randint()的缺陷。产生随机整数的快速方法。Random()直接使用getrandbits()和Numpy.random

  

目录

  前几天在写一个与差分隐私相关的简单程序时,我发现了一些奇怪的事情:Python的random.randint()函数相比其他随机数生成函数感觉很慢。由于randint()是Python中最常用的生成随机整数的API,所以我决定深入挖掘它的实现机制,了解它运行效率低的原因。

  本文深入讨论了随机模块的实现,并讨论了更为快速的产生伪随机整数的几种替代方法。

  

一、前言

  首先,我们可以观察random.randint()的运行效率:

  $ python 3-m time it-s import random random . random()

  10000000次循环,每次循环3:0.0523微秒

  $ python 3-m time it-s import random random . randint(0,128)

  1000000次循环,每次循环3:1.09微秒

  显然,生成一个大小为[0,128]的随机整数的开销是生成一个大小为[0,1]的随机浮点数的20倍左右。

  

二、对randint()运行效率的测试

  接下来我们就从python的源代码来分析randint()的实现机制。

  

三、从源码分析randint()的缺陷

  先说random()。该函数在Lib/Random.py文件中定义。函数random.random()是random类的Random方法的别名,random.random()直接从_Random继承Random方法。再往前追溯,我们会发现random方法的真正定义是在Modules/_randommodule.c中实现的,其实现代码如下:

  静态对象*

  random _ random(random object * self,PyObject *Py_UNUSED(忽略))

  {

  uint32_t a=genrand_int32(self)5,b=genrand _ int 32(self)6;

  返回py float _ from double((a * 67108864.0 b)*(1.0/9007199254740992.0));

  }

  getrand_int32()函数是用C语言实现的梅森旋转算法,可以快速生成伪随机数。

  综上所述,我们在Python中调用random.random()时,这个函数直接调用C函数,而这个C函数唯一的作用就是生成随机数,将genrand_int32()的结果转换成浮点数,没有任何额外的步骤。

  

random.random()

  现在我们来看看randint()的实现代码:

  def randint(self,a,b):

  """Return random integer in range [a, b], including both end points.

   """

   return self.randrange(a, b+1)

  randint函数会调用randrange()函数,因此我们再观察randrange()的源码。

  

def randrange(self, start, stop=None, step=1, _int=int):

   """Choose a random item from range(start, stop[, step]).

   This fixes the problem with randint() which includes the

   endpoint; in Python this is usually not what you want.

   """

   # This code is a bit messy to make it fast for the

   # common case while still doing adequate error checking.

   istart = _int(start)

   if istart != start:

   raise ValueError("non-integer arg 1 for randrange()")

   if stop is None:

   if istart > 0:

   return self._randbelow(istart)

   raise ValueError("empty range for randrange()")

   # stop argument supplied.

   istop = _int(stop)

   if istop != stop:

   raise ValueError("non-integer stop for randrange()")

   width = istop - istart

   if step == 1 and width > 0:

   return istart + self._randbelow(width)

   if step == 1:

   raise ValueError("empty range for randrange() (%d,%d, %d)" % (istart, istop, width))

   # Non-unit step argument supplied.

   istep = _int(step)

   if istep != step:

   raise ValueError("non-integer step for randrange()")

   if istep > 0:

   n = (width + istep - 1) // istep

   elif istep < 0:

   n = (width + istep + 1) // istep

   else:

   raise ValueError("zero step for randrange()")

   if n <= 0:

   raise ValueError("empty range for randrange()")

   return istart + istep*self._randbelow(n)

  在调用下一层的函数之前,randrange()需要对于函数参数进行大量的检查。不过,如果我们不是用stop参数,那么检查速度就会快一些,经过一堆检查之后,才可以调用_randbelow()方法。

  默认情况下,_randbelow()被映射到_randbelow_with_getrandbits()

  

def _randbelow_with_getrandbits(self, n):

   "Return a random int in the range [0,n). Raises ValueError if n==0."

   getrandbits = self.getrandbits

   k = n.bit_length() # dont use (n-1) here because n can be 1

   r = getrandbits(k) # 0 <= r < 2**k

   while r >= n:

   r = getrandbits(k)

   return r

  从该函数的源码可以发现:该函数的逻辑是计算出n的位数,而后按照位数生成随机比特,因此当n的大小不为2的次幂时,该函数可能需要多次调用getrandbits()getrandbits()是一个利用C语言定义的函数,该函数最终也会调用getrand_int32(),但由于该函数相对于 random() 函数需要更多的处理过程,导致其运行速度慢两倍。

  总而言之,通过python代码或者C代码都可以调用由C所定义的函数。由于 Python 是字节码解释的,因此,任何在调用C函数之前的,用python语言定义的处理过程,都会导致函数的运行速度比直接调用 C 函数慢很多。

  这里有几个实验可以帮助我们检验这个假设。首先,让我们尝试在randrange中通过调用没有stop参数的randrange来减少中间的参数检查过程,提高程序执行的速度:

  

$ python3 -m timeit -s import random random.randrange(1)

  1000000 loops, best of 3: 0.784 usec per loop

  正如预期的那样,由于中间运行过程的减少,此时randrange()运行时间比原始的randint()好一些。可以在 PyPy 中重新运行比较运行时间。

  

$ pypy -m timeit -s import random random.random()

  100000000 loops, best of 3: 0.0139 usec per loop

  $ pypy -m timeit -s import random random.randint(0, 128)

  100000000 loops, best of 3: 0.0168 usec per loop

  正如预期的那样,PyPy 中这些调用之间的差异很小。

  

  

四、更快的生成随机整数的方法

  所以 randint() 结果非常慢。当只需要生成少量随机数的时候,可以忽视该函数带来的性能损失,当需要生成大量的随机数时,就需要寻找一个效率够高的方法。

  

random.random()

  一个技巧就是使用random.random()代替,乘以我们的整数限制从而得到整数,由于random()可以生成均匀的[0,1)分布,因此扩展之后也可以得到整数上的均匀分布:

  

$ python3 -m timeit -s import random int(128 * random.random())

  10000000 loops, best of 3: 0.193 usec per loop

  这为我们提供了 [0, 128)范围内的伪随机整数,速度更快。需要注意的是:Python 以双精度表示其浮点数,精度为 53 位。当限制超过 53 位时,我们将使用此方法获得的数字不是完全随机的,多的位将丢失。如果不需要这么大的整数,就可以忽视这个问题。

  

  

直接使用 getrandbits()

  另一种生成伪随机整数的快速方法是直接使用 getrandbits():

  

$ python3 -m timeit -s import random random.getrandbits(7)

  10000000 loops, best of 3: 0.102 usec per loop

  此方法快速,但是生成数据范围有限:它支持的范围为[0,2^n]。如果我们想限制范围,取模的方法无法做到范围的限制——这会扭曲分布;因此,我们必须使用类似于上面示例中的_randbelow_with_getrandbits()中的循环。但是会减慢速度。

  

  

使用 Numpy.random

  最后,我们可以完全放弃 random 模块,而使用 Numpy:

  

$ python3 -m timeit -s import numpy.random numpy.random.randint(128)

  1000000 loops, best of 3: 1.21 usec per loop

  生成单个数据的速度很慢。那是因为 Numpy 不适合仅用于单个数据:numpy能够将成本摊销在用 C语言 创建or操作的大型数组上。为了证明这一点,下边给出了生成 100 个随机整数所需时间:

  

$ python3 -m timeit -s import numpy.random numpy.random.randint(128, size=100)

  1000000 loops, best of 3: 1.91 usec per loop

  仅比生成单个慢 60%! 每个整数 0.019 微秒,这是目前最快的方法——比调用random.random()快 3 倍。 这种方法如此之快的原因是Numpy将调用开销分摊到所有生成的整数上,并且在 Numpy 内部运行一个高效的 C 循环来生成它们。总之,如果要生成大量随机整数,建议使用 Numpy; 如果只是一次生成一个,它可能没有特别高效。

  到此这篇关于源码解析python中randint函数的效率缺陷的文章就介绍到这了,更多相关 python randint 内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: