Python 为什么将zip()返回的迭代器分配给变量会更快?
在以下代码中,time2-time1始终为负值:Python 为什么将zip()返回的迭代器分配给变量会更快?,python,python-3.x,Python,Python 3.x,在以下代码中,time2-time1始终为负值: import time a = [num for num in range(int(1e6))] b = [num for num in range(int(1e6))] start_time = time.time() e = [(c, d) for c, d in zip(a, b)] time1 = time.time() - start_time print("--- %s seconds ---" % (time1)) start
import time
a = [num for num in range(int(1e6))]
b = [num for num in range(int(1e6))]
start_time = time.time()
e = [(c, d) for c, d in zip(a, b)]
time1 = time.time() - start_time
print("--- %s seconds ---" % (time1))
start_time = time.time()
_zip = zip(a, b)
e = [(c, d) for c, d in _zip]
time2 = time.time() - start_time
print("--- %s seconds ---" % (time2))
print(time2-time1)
我认为这是因为在第一种情况下,我们需要调用zip()的次数要比在第二种情况下多得多。如果是这样的话,为什么zip不在每次调用iterable时都返回iterable中的第一个元素呢?zip()不是每次调用时都在a和b上创建新的迭代器吗?zip()是否对它创建的每个迭代器进行散列,并使用相同的散列存储迭代器以备将来调用
在对zip()调用进行迭代之前,将变量分配给它是好的还是坏的做法?性能提升通常值得额外的代码行吗?我尝试将两个版本的代码包装成函数,使用
timeit
正确地进行基准测试,并使用dis
检查生成的代码:
>>> import timeit
>>> def with_assignment():
... _zip = zip(a, b)
... return [(c, d) for c, d in _zip]
...
>>> def without_assignment():
... return [(c, d) for c, d in zip(a, b)]
...
>>> a, b = list(range(1000000)), list(range(1000000))
>>> timeit.timeit(with_assignment, number=100)
16.1892559
>>> timeit.timeit(without_assignment, number=100) # indeed, it's a little slower,
16.3349139
>>> timeit.timeit(with_assignment, number=100)
16.261616600000004
>>> timeit.timeit(without_assignment, number=100) # and consistently so
16.42448019999999
>>> import dis # So let's look under the hood:
>>> dis.dis(with_assignment)
2 0 LOAD_GLOBAL 0 (zip)
2 LOAD_GLOBAL 1 (a)
4 LOAD_GLOBAL 2 (b)
6 CALL_FUNCTION 2
8 STORE_FAST 0 (_zip)
3 10 LOAD_CONST 1 (<code object <listcomp> at 0x00000226ABCA08A0, file "<stdin>", line 3>)
12 LOAD_CONST 2 ('with_assignment.<locals>.<listcomp>')
14 MAKE_FUNCTION 0
16 LOAD_FAST 0 (_zip)
18 GET_ITER
20 CALL_FUNCTION 1
22 RETURN_VALUE
>>> dis.dis(without_assignment)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000226AD9299C0, file "<stdin>", line 2>)
2 LOAD_CONST 2 ('without_assignment.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (zip)
8 LOAD_GLOBAL 1 (a)
10 LOAD_GLOBAL 2 (b)
12 CALL_FUNCTION 2
14 GET_ITER
16 CALL_FUNCTION 1
18 RETURN_VALUE
>>>
导入timeit
>>>带有_赋值()的def:
... _zip=zip(a,b)
... 返回[(c,d)表示c,d在_-zip中]
...
>>>不带_赋值的def():
... 返回[(c,d)对于邮政编码中的c,d(a,b)]
...
>>>a,b=列表(范围(1000000)),列表(范围(1000000))
>>>timeit.timeit(带_赋值,数字=100)
16.1892559
>>>timeit.timeit(没有赋值,数字=100)#实际上,它有点慢,
16.3349139
>>>timeit.timeit(带_赋值,数字=100)
16.261616600000004
>>>timeit.timeit(没有赋值,数字=100)#并且一直如此
16.42448019999999
>>>导入dis#那么让我们看看引擎盖下面:
>>>dis.dis(带_分配)
2 0加载\u全局0(zip)
2负载_全局1(a)
4负载_全局2(b)
6调用函数2
8商店快0(_zip)
3 10加载常数1(<0x0000226ABCA08A0处的代码对象列表comp,文件“stdin”,第3行>)
12加载常数2('带有赋值…)
14生成函数0
16快速加载0(\u zip)
18获取ITER
20调用函数1
22返回值
>>>dis.dis(无分配)
2 0加载常数1(<0x0000226AD9299C0处的代码对象列表,文件“stdin”,第2行>)
2加载常数2('无分配…)
4生成函数0
6加载\u全局0(zip)
8负载_全局1(a)
10负载_全球2(b)
12调用函数2
14获取ITER
16调用函数1
18返回值
>>>
不过,恐怕我看不出明显的原因。除了额外的
STORE\u FAST
和LOAD\u FAST
(设置并使用本地\u zip
)之外,似乎所有的工作都在进行,尽管顺序不同。您应该使用标准库模块timeit
进行性能测试,我不认为这会改变这种情况下的结果。我真的不在乎时间是否有点短,我只想知道哪个更快,而且我个人认为这种方式更快。当我测试这个时,两个版本都没有一致的更快(正如预期的那样,片段之间的实际差异比随机变化和噪音的影响小得多)。真的吗?如果我将列表大小更改为1e8而不是1e6,则两者之间的差异约为8秒。它们之间的差别是巨大的。它看起来不像噪音,至少在我的机器上。无法复制。当我测试它时,两个版本的速度都不一致。