Python 3 中 len(set) 和 set.__len__() 的性能分析

13 投票
3 回答
22708 浏览
提问于 2025-04-17 09:48

在我分析我的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的情况下计算它们。

撰写回答