栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

为什么使用中间变量的代码要比不使用中间变量的代码快?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

为什么使用中间变量的代码要比不使用中间变量的代码快?

我的结果与您的结果相似:使用中间变量的代码在Python 3.4中始终一致地至少快10-20%。但是,当我在完全相同的Python
3.4解释器上使用IPython时,得到了以下结果:

In [1]: %timeit -n10000 -r20 tuple(range(2000)) == tuple(range(2000))10000 loops, best of 20: 74.2 µs per loopIn [2]: %timeit -n10000 -r20 a = tuple(range(2000));  b = tuple(range(2000)); a==b10000 loops, best of 20: 75.7 µs per loop

值得注意的是,当我

-mtimeit
从命令行使用时,我从未设法接近前者的74.2 µs 。

因此,这个Heisenbug变得非常有趣。我决定运行该命令,

strace
确实发生了一些麻烦:

% strace -o withoutvars python3 -m timeit "tuple(range(2000)) == tuple(range(2000))"10000 loops, best of 3: 134 usec per loop% strace -o withvars python3 -mtimeit "a = tuple(range(2000));  b = tuple(range(2000)); a==b"10000 loops, best of 3: 75.8 usec per loop% grep mmap withvars|wc -l46% grep mmap withoutvars|wc -l41149

现在,这是造成差异的一个很好的理由。不使用变量的代码导致

mmap
系统调用比使用中间变量的代码多近1000倍。

对于256k区域,其

withoutvars
mmap
/
munmap
。这些相同的行一遍又一遍地重复:

mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f32e56de000munmap(0x7f32e56de000, 262144)          = 0mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f32e56de000munmap(0x7f32e56de000, 262144)          = 0mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f32e56de000munmap(0x7f32e56de000, 262144)          = 0

mmap
通话似乎是从功能来
_PyObject_ArenaMmap
Objects/obmalloc.c
;
obmalloc.c
还包含宏
ARENA_SIZE
,这是
#define
d至是
(256 << 10)
(即
262144
);
类似地
munmap
匹配
_PyObject_ArenaMunmap
from
obmalloc.c

obmalloc.c

在Python 2.5之前,竞技场从未被使用过

free()
。从Python
2.5开始,我们确实尝试使用
free()
竞技场,并使用一些温和的启发式策略来增加最终释放竞技场的可能性。

因此,这些试探法以及Python对象分配器在清空后立即释放这些空闲区域的事实导致

python3 -mtimeit 'tuple(range(2000))== tuple(range(2000))'
触发病理行为,其中一个256 kiB内存区域被重新分配并重复释放。这种分配情况与
mmap
/
munmap
,这是因为他们的系统调用相对昂贵的-而且,
mmap
MAP_ANONYMOUS
要求新映射的页面必须清零-
尽管Python的也不会在意。

该行为在使用中间变量的代码中不存在,因为它使用了 更多的
内存,并且由于仍在其中分配了一些对象,因此无法释放任何内存空间。那是因为

timeit
它将使其循环成环

for n in range(10000)    a = tuple(range(2000))    b = tuple(range(2000))    a == b

现在的行为是,无论

a
b
将保持约束,直到他们重新分配*,所以在第二次迭代,
tuple(range(2000))
将分配一个3元组,并分配
a =tuple(...)
将降低旧的元组的引用计数,导致它被释放,并提高新元组的引用计数;然后发生同样的事情
b
。因此,在第一次迭代之后,这些元组中始终至少有2个(如果不是3个),因此不会发生颠簸。

最值得注意的是,不能保证使用中间变量的代码总是更快-实际上,在某些设置中,使用中间变量可能会导致额外的

mmap
调用,而直接比较返回值的代码可能没问题。


有人问为什么

timeit
禁用垃圾收集时会发生这种情况。确实是这样
timeit
做的:

注意

默认情况下,

timeit()
在计时期间临时关闭垃圾收集。这种方法的优势在于,它使独立计时更具可比性。这个缺点是GC可能是被测功能性能的重要组成部分。如果是这样,则可以将GC作为设置字符串中的第一条语句重新启用。例如:

但是,Python的垃圾收集器仅用于回收 循环垃圾 ,即引用形成循环的对象的集合。这里不是这种情况。而是当引用计数降至零时立即释放这些对象。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/375782.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号