【问题标题】:Surprising results with Python timeit: Counter() vs defaultdict() vs dict()Python timeit 的惊人结果:Counter() vs defaultdict() vs dict()
【发布时间】:2015-03-04 07:32:07
【问题描述】:

我使用 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()。

【问题讨论】:

    标签: python python-2.7 counter timeit defaultdict


    【解决方案1】:

    是的,这是意料之中的; Counter() 构造函数使用Counter.update(),它使用self.get()加载初始值,而不是依赖__missing__

    此外,defaultdict__missing__ 工厂完全在 C 代码中处理,尤其是在使用像 int() 这样本身在 C 中实现的另一种类型时。Counter 源是纯 Python,因此 @987654330 @ 方法需要 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() 方法的常规字典,以实现最大速度。但是您还必须重新实现 CounterCounter.most_common() 方法的包行为。 defaultdict 用例不胜枚举。

    在 Python 3.2 中,更新 Counter() 通过添加处理这种情况的 C 库来提高速度;见issue 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 秒)。

    【讨论】:

    • 自 2020 年起使用 python 3.7 >= 不再是这种情况 - 计数器更快
    • @bones.felipe 请阅读完整答案。问题是关于 Python 2.7 的,但我提到从 Python 3.2 开始,计数器更快,所以截至 2012 年(8 年前)。
    猜你喜欢
    • 1970-01-01
    • 2013-07-14
    • 2012-06-03
    • 2021-10-20
    • 2018-01-09
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多