2012-01-08 76 views
5

在分析我的Python应用程序时,我发现len()在使用集合时似乎是非常昂贵的。请参见下面的代码:在Python 3中剖析len(set)与set .__ 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(),但代码看上去很脏:(

+7

你为什么用'cProfile'而不是'timeit'?前者是为了在大型程序中寻找瓶颈,并为小规模牺牲一些准确性。后者用于相对精确地测量小片段的整体性能。 'timeit'应该是像这样的微基准的首选。对我而言,它表示一个较小的差异(每个“len”调用0.0879μs,每个“.__ len__”调用0.158μs=“len”减慢70%)。 – delnan 2012-01-08 15:33:49

+0

谢谢@delnan,我在Python中很新。使用'timeit'我也得到了类似的比例。事实上,我的程序比上面的代码大得多,但是让我感到惊讶的是'len()'函数是主要的瓶颈之一。好的,所以我会忽略'len()'并专注于我自己的功能,对吧? – Tregoreg 2012-01-08 15:56:59

回答

13

显然,len具有一些开销,因为它的功能调用和转换AttributeErrorTypeError。此外,set.__len__是,它必然要这样简单的操作非常快相比,任何事情,但我仍然没有找到像14X差什么用timeit时:

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的情况下进行计算。

+0

+1:特别是不要过早优化。基准可能有缺陷,正如你现在看到的,三个基准可能会返回三个不同的结果;你最终可能会以与这个微基准测试完全不同的东西为基准。有意思的是,'len'不能更快,因为它调用'__len__'。但是,这一切都是确定的。 – 2012-01-08 16:21:08

+2

@ Anony-Mousse:实际上,我只是看了一遍自己的结果,现在我才看到'len'比'__len__'快。不知道这是怎么回事。 – 2012-01-08 16:51:25

+2

'.__ len__'也执行函数调用,*和*必须查找属性。这超过了'len'的全局查找。 – WolframH 2012-02-11 13:30:22

1

这将是一个评论,但在larsman对他有争议的结果和我得到的结果发表评论后,我认为将我的数据添加到该线程很有意思。

试图或多或少相同的设置我得到相反的OP得到的,并且在由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") 

这是ActivePython的2.6。 7 64bit在win7中

3

这是关于profiler的一个有趣的观察,它与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) 

探查超时每一次调用从lenAlen,但整体计时为lenB。定时调用总是会增加一些开销;在lenA的情况下,你会看到这种开销增加了一百万倍。

+1

我认为你的观点绝对精确。这完全是关于'cProfile'的开销,而不是'len'函数的性能。 – Tregoreg 2012-01-20 00:57:23