Python 3 中 len(set) 和 set.__len__() 的性能分析
在我分析我的Python应用程序性能的时候,我发现使用集合(sets)时,len()
这个函数似乎非常耗时。看看下面的代码:
import cProfile
def lenA(s):
for i in range(1000000):
len(s);
def lenB(s):
for i in range(1000000):
s.__len__();
def main():
s = set();
lenA(s);
lenB(s);
if __name__ == "__main__":
cProfile.run("main()","stats");
根据下面的性能分析数据,lenA()
的速度似乎比lenB()
慢了14倍:
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA)
1000000 1.845 0.000 1.845 0.000 {built-in method len}
1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB)
我是不是漏掉了什么?现在我使用__len__()
来代替len()
,但是代码看起来有点乱 :(
3 个回答
1
本来我打算把这当作评论,但在看到larsman对他那些有争议的结果的评论后,以及我得到的结果,我觉得把我的数据加到这个讨论里挺有意思的。
我尝试了差不多相同的设置,结果却和提问者得到的结果正好相反,而且和larsman评论的方向一致:
12.1964105975 <- __len__
6.22144670823 <- len()
C:\Python26\programas>
测试内容:
def lenA(s):
for i in range(100):
len(s);
def lenB(s):
for i in range(100):
s.__len__();
s = set()
if __name__ == "__main__":
from timeit import timeit
print timeit("lenB(s)", setup="from __main__ import lenB, s")
print timeit("lenA(s)", setup="from __main__ import lenA, s")
这是在win7系统上运行的activepython 2.6.7 64位版本。
7
这是一个关于性能分析工具的有趣观察,跟实际的 len
函数性能没有关系。你看,在性能分析的统计数据中,有两行是关于 lenA
的:
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.986 1.986 3.830 3.830 .../lentest.py:5(lenA)
1000000 1.845 0.000 1.845 0.000 {built-in method len}
...而关于 lenB
只有一行:
1 0.273 0.273 0.273 0.273 .../lentest.py:9(lenB)
性能分析工具对每一次从 lenA
调用 len
的情况进行了计时,但对 lenB
的计时是整体的。每次调用计时都会增加一些额外的开销;在 lenA
的情况下,你会看到这种开销被放大了上百万倍。
22
显然,len
这个函数会有一些额外的开销,因为它需要进行函数调用,并且会把AttributeError
转换成TypeError
。另外,set.__len__
这个操作非常简单,所以和其他操作相比,它的速度肯定很快。不过,我在使用timeit
的时候,还是没有发现像14倍那么大的差距:
In [1]: s = set()
In [2]: %timeit s.__len__()
1000000 loops, best of 3: 197 ns per loop
In [3]: %timeit len(s)
10000000 loops, best of 3: 130 ns per loop
你应该始终使用len
,而不是__len__
。如果在你的程序中,调用len
成为了性能瓶颈,那你就需要重新考虑程序的设计,比如可以在某个地方缓存大小,或者在不调用len
的情况下计算它们。