python 2 与 python 3 的随机性能,特别是 `random.sample` 和 `random.shuffle`
Posted
技术标签:
【中文标题】python 2 与 python 3 的随机性能,特别是 `random.sample` 和 `random.shuffle`【英文标题】:python 2 vs python 3 performance of random, particularly `random.sample` and `random.shuffle` 【发布时间】:2017-09-23 03:25:23 【问题描述】:python random 模块的性能问题,特别是random.sample
和random.shuffle
出现在this question 中。在我的电脑上,我得到以下结果:
> python -m timeit -s 'import random' 'random.randint(0,1000)'
1000000 loops, best of 3: 1.07 usec per loop
> python3 -m timeit -s 'import random' 'random.randint(0,1000)'
1000000 loops, best of 3: 1.3 usec per loop
这比 python3 与 python2 的性能下降了 20% 以上。情况会变得更糟。
> python -m timeit -s 'import random' 'random.shuffle(list(range(10)))'
100000 loops, best of 3: 3.85 usec per loop
> python3 -m timeit -s 'import random' 'random.shuffle(list(range(10)))'
100000 loops, best of 3: 8.04 usec per loop
> python -m timeit -s 'import random' 'random.sample(range(10),3)'
100000 loops, best of 3: 2.4 usec per loop
> python3 -m timeit -s 'import random' 'random.sample(range(10),3)'
100000 loops, best of 3: 6.49 usec per loop
这表示random.shuffle
的性能下降了 100%,random.sample
的性能下降了几乎 200%。这是相当严重的。
我在上面的测试中使用了python 2.7.9和python 3.4.2。
我的问题:python3 中的random
模块发生了什么?
【问题讨论】:
可能值得用更新的 Python 3 版本重新测试。据我了解,过去几年修复了很多性能回归问题。 Python 3.4.2 已经两年半了。在我自己的系统上进行测试,在randint
测试中,3.6.1 比 3.4.3 快 35%。
@Blckknght -- 那肯定没有帮助。我刚刚升级到 Python 3.6.1,这让情况变得更糟:random.randint
测试的循环为 1.43 微秒,random.shuffle
测试的循环为 8.37 微秒,random.sample
测试没有任何变化。
【参考方案1】:
----------- 改变了什么 ------------------------------- ----------------
发生了几件事:
整数在 int/long 统一中的效率降低。这也是为什么整数现在是 28 字节宽而不是 64 位 Linux/MacOS 版本上的 24 字节的原因。
通过使用_randbelow
,随机播放变得更加准确。这消除了之前算法中的细微偏差。
索引变得更慢,因为整数索引的特殊情况已从 ceval.c 中删除,主要是因为较新的整数更难处理,而且一些核心开发人员没有不认为优化是值得的。
range() 函数已替换为 xrange()。这是相关的,因为 OP 的计时都在内循环中使用 range()。
shuffle() 和 sample() 的算法没有改变。
Python 3 进行了许多更改,例如 unicode-everywhere,这使得内部结构更复杂、速度更慢、内存更密集。作为回报,Python 3 让用户更容易编写正确的代码。
同样,int/long 统一使语言更简单,但以速度和空间为代价。在 random 模块中改用 _randbelow()
会产生运行时成本,但会在准确性和正确性方面受益。
------------ 结论 -------------------------------- ------------------
简而言之,Python 3 在某些对许多用户来说很重要的方面更好,而在某些人们很少注意到的方面则更糟。工程通常需要权衡。
------------ 详情-------------------------------- -------------------------
shuffle()的Python2.7代码:
def shuffle(self, x, random=None):
if random is None:
random = self.random
_int = int
for i in reversed(xrange(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = _int(random() * (i+1))
x[i], x[j] = x[j], x[i]
shuffle()的Python3.6代码:
def shuffle(self, x, random=None):
if random is None:
randbelow = self._randbelow
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = randbelow(i+1) # <-- This part changed
x[i], x[j] = x[j], x[i]
else:
_int = int
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = _int(random() * (i+1))
x[i], x[j] = x[j], x[i]
Python 2.7 整数大小:
>>> import sys
>>> sys.getsizeof(1)
24
Python 3.6 整数大小:
>>> import sys
>>> sys.getsizeof(1)
28
索引查找的相对速度(使用整数参数索引到列表中的二进制订阅):
$ python2.7 -m timeit -s 'a=[0]' 'a[0]'
10000000 loops, best of 3: 0.0253 usec per loop
$ python3.6 -m timeit -s 'a=[0]' 'a[0]'
10000000 loops, best of 3: 0.0313 usec per loop
ceval.c 中的 Python 2.7 代码对索引查找进行了优化:
TARGET_NOARG(BINARY_SUBSCR)
w = POP();
v = TOP();
if (PyList_CheckExact(v) && PyInt_CheckExact(w))
/* INLINE: list[int] */
Py_ssize_t i = PyInt_AsSsize_t(w);
if (i < 0)
i += PyList_GET_SIZE(v);
if (i >= 0 && i < PyList_GET_SIZE(v))
x = PyList_GET_ITEM(v, i);
Py_INCREF(x);
else
goto slow_get;
else
slow_get:
x = PyObject_GetItem(v, w);
Py_DECREF(v);
Py_DECREF(w);
SET_TOP(x);
if (x != NULL) DISPATCH();
break;
ceval.c 中的 Python 3.6 代码,没有针对索引查找进行优化:
TARGET(BINARY_SUBSCR)
PyObject *sub = POP();
PyObject *container = TOP();
PyObject *res = PyObject_GetItem(container, sub);
Py_DECREF(container);
Py_DECREF(sub);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
【讨论】:
AFAICT,_randbelow
现在实际上更快(-m timeit -s 'import random; rb = random.randrange.__self__._randbelow' 'rb(1000)'
在我的 Py3 上运行速度是我的 Py2 的两倍)。 randrange
和 randint
变慢的原因是因为以前,它们对小于使用 random.random
的 Python 的 float
精度的数字有一个快速的路径,这甚至更快。在 Py3 中,这条快速路径被移除了,所以即使 _randbelow
变得更快,将它用于小数字也会让一切都变慢一点。
"Python 3 让用户更容易编写正确的代码。"仍然被许多人低估
因为编写以前在大多数情况下都有效的糟糕代码非常容易。该死的 Python 让用户思考!以上是关于python 2 与 python 3 的随机性能,特别是 `random.sample` 和 `random.shuffle`的主要内容,如果未能解决你的问题,请参考以下文章
性能:Python 3.x 与 Python 2.x [关闭]
python 如何生成 2 1 4 3 6 5 8 7。。。的指定长度的序列?