Python 中的生成器与列表理解性能
Generators vs List Comprehension performance in Python
目前我正在学习生成器和列表理解,并与探查器混在一起以查看性能提升偶然发现这个 cProfile 中使用两者的大范围素数总和。
我可以看到在生成器中 :1 genexpr 的累积时间比其列表中的对应项短得多,但第二行让我感到困惑。正在执行一个我认为检查数字是否为质数的调用,但不应该是列表理解中的另一个 :1 模块?
我是否遗漏了个人资料中的内容?
In [8]: cProfile.run('sum((number for number in xrange(9999999) if number % 2 == 0))')
5000004 function calls in 1.111 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5000001 0.760 0.000 0.760 0.000 <string>:1(<genexpr>)
1 0.000 0.000 1.111 1.111 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.351 0.351 1.111 1.111 {sum}
In [9]: cProfile.run('sum([number for number in xrange(9999999) if number % 2 == 0])')
3 function calls in 1.123 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.075 1.075 1.123 1.123 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.048 0.048 0.048 0.048 {sum}
首先调用生成器对象的 next
(或 Python 中的 __next__
3)方法,而不是进行偶数检查。
在 Python 2 中你不会得到任何额外的列表理解(LC)行,因为 LC 没有创建任何对象,但在 Python 3 中你会因为现在要它类似于生成器表达式,也为 LC 创建了一个附加代码对象 (<listcomp>
)。
>>> cProfile.run('sum([number for number in range(9999999) if number % 2 == 0])')
5 function calls in 1.751 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.601 1.601 1.601 1.601 <string>:1(<listcomp>)
1 0.068 0.068 1.751 1.751 <string>:1(<module>)
1 0.000 0.000 1.751 1.751 {built-in method exec}
1 0.082 0.082 0.082 0.082 {built-in method sum}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
>>> cProfile.run('sum((number for number in range(9999999) if number % 2 == 0))')
5000005 function calls in 2.388 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5000001 1.873 0.000 1.873 0.000 <string>:1(<genexpr>)
1 0.000 0.000 2.388 2.388 <string>:1(<module>)
1 0.000 0.000 2.388 2.388 {built-in method exec}
1 0.515 0.515 2.388 2.388 {built-in method sum}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
虽然 1(LC) 的调用次数与生成器表达式中的 5000001 不同,但这是最多的,因为 sum
正在使用迭代器,因此必须调用其 __next__
方法 500000 + 1次(最后 1 可能是 StopIteration
结束迭代)。对于列表理解,所有的魔法都发生在它的代码对象中,其中 LIST_APPEND
帮助它一个接一个地附加到列表中,即没有对 cProfile
的可见调用。
目前我正在学习生成器和列表理解,并与探查器混在一起以查看性能提升偶然发现这个 cProfile 中使用两者的大范围素数总和。
我可以看到在生成器中 :1 genexpr 的累积时间比其列表中的对应项短得多,但第二行让我感到困惑。正在执行一个我认为检查数字是否为质数的调用,但不应该是列表理解中的另一个 :1 模块?
我是否遗漏了个人资料中的内容?
In [8]: cProfile.run('sum((number for number in xrange(9999999) if number % 2 == 0))')
5000004 function calls in 1.111 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5000001 0.760 0.000 0.760 0.000 <string>:1(<genexpr>)
1 0.000 0.000 1.111 1.111 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.351 0.351 1.111 1.111 {sum}
In [9]: cProfile.run('sum([number for number in xrange(9999999) if number % 2 == 0])')
3 function calls in 1.123 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.075 1.075 1.123 1.123 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.048 0.048 0.048 0.048 {sum}
首先调用生成器对象的 next
(或 Python 中的 __next__
3)方法,而不是进行偶数检查。
在 Python 2 中你不会得到任何额外的列表理解(LC)行,因为 LC 没有创建任何对象,但在 Python 3 中你会因为现在要它类似于生成器表达式,也为 LC 创建了一个附加代码对象 (<listcomp>
)。
>>> cProfile.run('sum([number for number in range(9999999) if number % 2 == 0])')
5 function calls in 1.751 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.601 1.601 1.601 1.601 <string>:1(<listcomp>)
1 0.068 0.068 1.751 1.751 <string>:1(<module>)
1 0.000 0.000 1.751 1.751 {built-in method exec}
1 0.082 0.082 0.082 0.082 {built-in method sum}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
>>> cProfile.run('sum((number for number in range(9999999) if number % 2 == 0))')
5000005 function calls in 2.388 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5000001 1.873 0.000 1.873 0.000 <string>:1(<genexpr>)
1 0.000 0.000 2.388 2.388 <string>:1(<module>)
1 0.000 0.000 2.388 2.388 {built-in method exec}
1 0.515 0.515 2.388 2.388 {built-in method sum}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
虽然 1(LC) 的调用次数与生成器表达式中的 5000001 不同,但这是最多的,因为 sum
正在使用迭代器,因此必须调用其 __next__
方法 500000 + 1次(最后 1 可能是 StopIteration
结束迭代)。对于列表理解,所有的魔法都发生在它的代码对象中,其中 LIST_APPEND
帮助它一个接一个地附加到列表中,即没有对 cProfile
的可见调用。