Python 使用for循环与列表理解来优化代码
为什么使用for循环的几乎全部比使用列表理解的几乎全部更有效?他们肯定都是O(n)? 编辑:另一个问题的答案是非具体的,基本上说一个比另一个更快,这取决于具体情况。那么这个案子呢Python 使用for循环与列表理解来优化代码,python,python-3.x,optimization,list-comprehension,Python,Python 3.x,Optimization,List Comprehension,为什么使用for循环的几乎全部比使用列表理解的几乎全部更有效?他们肯定都是O(n)? 编辑:另一个问题的答案是非具体的,基本上说一个比另一个更快,这取决于具体情况。那么这个案子呢 def almost_all_a(numbers): sumall = sum(numbers) rangelen = range(len(numbers)) return [sumall - numbers[i] for i in rangelen] def almost_all_b(num
def almost_all_a(numbers):
sumall = sum(numbers)
rangelen = range(len(numbers))
return [sumall - numbers[i] for i in rangelen]
def almost_all_b(numbers):
sumall = sum(numbers)
for i in range(len(numbers)):
numbers[i] = sumall - numbers[i]
相关问题的答案包罗万象,但不知何故被隐藏了起来 让我们看看什么是
几乎所有的:它构建一个与原始列表大小相同的新列表,然后返回该新列表。对于大型列表,这将使用两倍于列表所需的内存(假设此处为数字列表)。如果您这样调用函数:nums=small\u a(nums)
,您只是在构建一个新列表,完成后会丢弃上一个列表。两个性能影响:需要(临时)内存,并且需要垃圾收集器清理旧列表
在
几乎所有
中,这一切都没有发生:您只是在适当地更改列表元素:没有额外的分配(内存增益)和收集(执行时间增益)
TL/DR:是什么让<代码>版本丢失,它归结为分配一个新列表,而
使用列表理解代替不构建列表的循环,无意义地累积一个无意义值的列表,然后扔掉该列表,由于创建和扩展列表的开销,通常速度较慢
您的复杂性分析是正确的:
n
计算总和的操作加上n
在这两种情况下计算列表的操作使O(n)
但是在我们谈论速度之前,你肯定已经注意到,几乎所有的b
都有副作用,而几乎所有的a
都没有副作用。更糟糕的是,几乎所有的b
都不是幂等的。如果反复调用几乎所有的,则每次都会修改参数数字除非你有很好的理由,否则你应该更喜欢几乎所有的a
而不是几乎所有的b
,因为它更容易理解,也不容易出错
基准1
我将尝试用timeit
确认您的断言(small\u a
[比small\u b
更有效):
>>> from timeit import timeit
>>> ns=list(range(100))
>>> timeit(lambda: almost_all_a(ns), number=10000)
0.06381335399782984
>>> timeit(lambda: almost_all_b(ns), number=10000)
2.3228586789991823
哇几乎所有的a
大约比几乎所有的b
快35倍!!!不,那是个玩笑。你可以看到发生了什么:几乎所有的都被应用了10000次到[1,…,90]
,并产生了副作用,因此数字急剧增加:
>>> len(str(ns[0])) # number of digits of the first element!
19959
好的,那只是为了说服你避免使用有副作用的函数
基准2
现在,真正的考验是:
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_a})
5.720672591000039
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_b})
5.937547881
请注意,基准可能会在另一个列表或另一个平台上给出不同的结果。(想想如果列表占用了90%的可用RAM会发生什么。)但是让我们假设我们可以概括
Python字节码
让我们看看字节码:
>>> import dis
>>> dis.dis(almost_all_a)
2 0 LOAD_GLOBAL 0 (sum)
2 LOAD_DEREF 0 (numbers)
4 CALL_FUNCTION 1
6 STORE_DEREF 1 (sumall)
3 8 LOAD_GLOBAL 1 (range)
10 LOAD_GLOBAL 2 (len)
12 LOAD_DEREF 0 (numbers)
14 CALL_FUNCTION 1
16 CALL_FUNCTION 1
18 STORE_FAST 1 (rangelen)
4 20 LOAD_CLOSURE 0 (numbers)
22 LOAD_CLOSURE 1 (sumall)
24 BUILD_TUPLE 2
26 LOAD_CONST 1 (<code object <listcomp> at 0x7fdc551dee40, file "<stdin>", line 4>)
28 LOAD_CONST 2 ('almost_all_a.<locals>.<listcomp>')
30 MAKE_FUNCTION 8
32 LOAD_FAST 1 (rangelen)
34 GET_ITER
36 CALL_FUNCTION 1
38 RETURN_VALUE
开始几乎是一样的。然后,你有一个列表理解,就像一个黑匣子。如果我们打开该框,我们会看到:
>>> dis.dis(almost_all_a.__code__.co_consts[1])
4 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 STORE_FAST 1 (i)
8 LOAD_DEREF 1 (sumall)
10 LOAD_DEREF 0 (numbers)
12 LOAD_FAST 1 (i)
14 BINARY_SUBSCR
16 BINARY_SUBTRACT
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
你有两个不同点:
- 在列表理解中,
sumall
和numbers
加载的是LOAD\u DEREF
,而不是LOAD\u FAST
(这对于闭包来说是正常的),应该稍微慢一点李>
- 在列表理解中,
list\u APPEND
将赋值替换为numbers[i]
(LOAD\u FAST(numbers)/LOAD\u FAST(i)/STORE\u SUBSCR
第36-40行)
我的猜测是,开销来自于那个任务
另一个基准
您可以将几乎所有的重写得更加整洁,因为您不需要索引:
def almost_all_c(numbers):
sumall = sum(numbers)
return [sumall - n for n in numbers]
这个版本(在我的示例+平台上)比几乎所有更快:
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_a})
5.755438814000172
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_b})
5.93645353099987
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_c})
4.571863283000084
(注意,在Python中,整洁的版本通常是最快的。)几乎所有的和几乎所有的之间的区别在于使用了对i
-第numbers
项的访问权(您可以对要检查的几乎所有的进行反编译)
结论
我觉得这里的瓶颈是访问I
-第项编号
:
- 一次在
几乎所有的李>
- 在
中有两次几乎全部李>
- 从不在
几乎所有的\u c
中
这就是为什么几乎所有的a
比几乎所有的b
快的原因。可能的重复:嗨,谢谢你,我仍然习惯于堆栈溢出,但是我仍然看不出在这种情况下列表迭代如何更有效?第二种算法无法通过较大列表的测试。
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_a})
5.755438814000172
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_b})
5.93645353099987
>>> timeit('ns=list(range(100));almost_all(ns)', globals={'almost_all':almost_all_c})
4.571863283000084