![](/img/trans.png)
[英]Why (in Python) is random.randint so much slower than random.random?
[英]Why random.randint() is much slower than random.getrandbits()
A 做了一個測試,比較了 Python 中的random.randint()
和random.getrandbits()
。 結果表明getrandbits
比randint
。
from random import randint, getrandbits, seed
from datetime import datetime
def ts():
return datetime.now().timestamp()
def diff(st, en):
print(f'{round((en - st) * 1000, 3)} ms')
seed(111)
N = 6
rn = []
st = ts()
for _ in range(10**N):
rn.append(randint(0, 1023))
en = ts()
diff(st,en)
rn = []
st = ts()
for _ in range(10**N):
rn.append(getrandbits(10))
en = ts()
diff(st,en)
數字范圍完全相同,因為10
隨機位的范圍從0
(在0000000000
的情況下)到1023
(在1111111111
的情況下)。
output的代碼:
590.509 ms
91.01 ms
可以看到,相同條件下的getrandbits
幾乎快了6.5
倍。 但為什么? 有人可以解釋一下嗎?
randint
使用randrange
,它使用_randbelow
,即_randbelow_with_get_randbits
(如果存在),它使用getrandbits
。 但是, randrange
包括對getrandbits
的基礎調用之上的開銷,因為它必須檢查開始和步驟 arguments。 即使有這些檢查的快速路徑,它們仍然存在。 使用randint
時肯定還有其他因素導致 6.5 減速,但這個答案至少向您表明randint
總是比getrandbits
慢。
getrandbits()
是一個非常短的 function 幾乎立即進入本機代碼:
def getrandbits(self, k):
"""getrandbits(k) -> x. Generates an int with k random bits."""
if k < 0:
raise ValueError('number of bits must be non-negative')
numbytes = (k + 7) // 8 # bits / 8 and rounded up
x = int.from_bytes(_urandom(numbytes), 'big')
return x >> (numbytes * 8 - k) # trim excess bits
_urandom
是os.urandom
,您甚至無法在同一文件夾中的os.py
中找到它,因為它是本機代碼。 在此處查看詳細信息: 我在哪里可以找到 os.urandom() 的源代碼?
randint()
是一個return self.randrange(a, b+1)
調用,其中randrange()
是一個長而通用的 function,將近 100 行 Python 代碼,它的“快速通道”以return istart + self._randbelow(width)
經過大約 50 行檢查和初始化。
_randbelow()
實際上是一種選擇,具體取決於您的情況中是否存在getrandbits()
,所以可能this
正在運行:
def _randbelow_with_getrandbits(self, n):
"Return a random int in the range [0,n). Returns 0 if n==0."
if not n:
return 0
getrandbits = self.getrandbits
k = n.bit_length() # don't use (n-1) here because n can be 1
r = getrandbits(k) # 0 <= r < 2**k
while r >= n:
r = getrandbits(k)
return r
所以randint()
getrandbits()
結束,這幾乎就是它比它慢的原因。
The underlying reason is the function that getrandbits
calls is written in C
, look at the source code at GitHub Python repository
你會發現getrandbits
在后台調用了os
模塊中的urandom
,如果你看一下這個function的實現,你會發現它確實是用C寫的。
os_urandom_impl(PyObject *module, Py_ssize_t size)
/*[clinic end generated code: output=42c5cca9d18068e9 input=4067cdb1b6776c29]*/
{
PyObject *bytes;
int result;
if (size < 0)
return PyErr_Format(PyExc_ValueError,
"negative argument not allowed");
bytes = PyBytes_FromStringAndSize(NULL, size);
if (bytes == NULL)
return NULL;
result = _PyOS_URandom(PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
if (result == -1) {
Py_DECREF(bytes);
return NULL;
}
return bytes;
}
說到randint
,如果你看一下這個 function 的源代碼,它調用randrange
,它進一步調用_randbelow_with_get_randbits
。
def randrange(self, start, stop=None, step=_ONE):
"""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.
try:
istart = _index(start)
except TypeError:
istart = int(start)
if istart != start:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer arg 1 for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
if stop is None:
# We don't check for "step != 1" because it hasn't been
# type checked and converted to an integer yet.
if step is not _ONE:
raise TypeError('Missing a non-None stop argument')
if istart > 0:
return self._randbelow(istart)
raise ValueError("empty range for randrange()")
# stop argument supplied.
try:
istop = _index(stop)
except TypeError:
istop = int(stop)
if istop != stop:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer stop for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
width = istop - istart
try:
istep = _index(step)
except TypeError:
istep = int(step)
if istep != step:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer step for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
# Fast path.
if istep == 1:
if width > 0:
return istart + self._randbelow(width)
raise ValueError("empty range for randrange() (%d, %d, %d)" % (istart, istop, width))
# Non-unit step argument supplied.
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)
def randint(self, a, b):
"""Return random integer in range [a, b], including both end points.
"""
return self.randrange(a, b+1)
正如你所看到的,在randrange
function 和 Python 中的Try
/ Except
塊中發生了很多事情,由於所有這些開銷和控制流,它很慢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.