簡體   English   中英

Python timeit 的驚人結果:Counter() vs defaultdict() vs dict()

[英]Surprising results with Python timeit: Counter() vs defaultdict() vs dict()

我用 timeit 獲得了非常令人驚訝的結果,有人能告訴我我做錯了什么嗎? 我正在使用 Python 2.7。

這是文件 speedtest_init.py 的內容:

import random

to_count = [random.randint(0, 100) for r in range(60)]

這些是speedtest.py的內容:

__author__ = 'BlueTrin'

import timeit

def test_init1():
    print(timeit.timeit('import speedtest_init'))

def test_counter1():
    s = """\
    d = defaultdict(int);
    for i in speedtest_init.to_count:
        d[i] += 1
    """
    print(timeit.timeit(s, 'from collections import defaultdict; import speedtest_init;'))

def test_counter2():
    print(timeit.timeit('d = Counter(speedtest_init.to_count);', 'from collections import Counter; import speedtest_init;'))


if __name__ == "__main__":
    test_init1()
    test_counter1()
    test_counter2()

控制台輸出是:

C:\Python27\python.exe C:/Dev/codility/chlorum2014/speedtest.py
2.71501962931
65.7090444503
91.2953839048

Process finished with exit code 0

我認為默認情況下 timeit() 運行代碼的 1000000 倍,所以我需要將時間除以 1000000,但令人驚訝的是 Counter 比 defaultdict() 慢。

這是預期的嗎?

編輯:

同樣使用 dict 比 defaultdict(int) 更快:

def test_counter3():
    s = """\
    d = {};
    for i in speedtest_init.to_count:
        if i not in d:
            d[i] = 1
        else:
            d[i] += 1
    """
    print(timeit.timeit(stmt=s, setup='from collections import defaultdict; import speedtest_init;')

最后一個版本比 defaultdict(int) 更快,這意味着除非您更關心可讀性,否則您應該使用 dict() 而不是 defaultdict()。

是的,這是意料之中的; Counter()構造函數使用Counter.update() ,它使用self.get()加載初始值而不是依賴__missing__

此外, defaultdict __missing__工廠完全在 C 代碼中處理,尤其是在使用另一種類型時,如int()本身是在 C 中實現的。 Counter源是純 Python,因此Counter.__missing__方法需要一個 Python 框架來執行。

因為dict.get()仍然在 C 中處理,構造函數方法是Counter()的更快方法,前提是您使用相同的技巧Counter.update()使用並將self.get查找別名為本地優先:

>>> import timeit
>>> import random
>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=9, releaselevel='final', serial=0)
>>> to_count = [random.randint(0, 100) for r in range(60)]
>>> timeit.timeit('for i in to_count: c[i] += 1',
...               'from collections import Counter; from __main__ import to_count; c = Counter()',
...               number=10000)
0.2510359287261963
>>> timeit.timeit('for i in to_count: c[i] = c_get(i, 0) + 1',
...               'from collections import Counter; from __main__ import to_count; c = Counter(); c_get = c.get',
...               number=10000)
0.20978617668151855

defaultdictCounter都是為其功能而非性能而構建的有用類; 不依賴__missing__鈎子仍然可以更快:

>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
...               'from __main__ import to_count; d = {}; d_get = d.get',
...               number=10000)
0.11437392234802246

這是一個使用別名dict.get()方法以獲得最大速度的常規字典。 但是,您還必須重新實現Counter的 bag 行為或Counter.most_common()方法。 defaultdict用例遠遠超出了計數。

在 Python 3.2 中,通過添加處理這種情況的 C 庫,更新Counter()獲得了速度提升; 請參閱問題 10667 在 Python 3.4 上測試, Counter()構造函數現在擊敗了別名dict.get案例:

>>> timeit.timeit('Counter(to_count)',
...               'from collections import Counter; from __main__ import to_count',
...               number=100000)
0.8332311600097455
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1',
...               'from __main__ import to_count; d = {}; d_get = d.get',
...               number=100000)
0.961191965994658
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0)

(注意:為了獲得有意義的計時結果,迭代次數從 10k 增加到 100k;因此,如果將這些與上面的dict.get()情況進行比較,則需要將計時時間乘以 10,即 1.144 秒)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM