2012-01-08 75 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