`in`对`__contains__`有多少优化?

gab*_*ton 3 python python-3.x

所以在itertools recipe部分,他们有一段代码如下:

seen = set()
seen_add = seen.add
Run Code Online (Sandbox Code Playgroud)

我想知道一个类似的想法是否可能弥合in和之间的一些性能差距__contains__.例如,使用以下代码:

seen = set()
seen_add = seen.add
in_seen = seen.__contains__
for item in iterable:
    in_seen(item)
Run Code Online (Sandbox Code Playgroud)

VS

seen = set()
seen_add = seen.add
in_seen = seen.__contains__  # make identical in beginning
for item in iterable:
    item in seen
Run Code Online (Sandbox Code Playgroud)

所以,如果我正确地读取dis的输出,问题归结为" x in y比快func(x)?"

编辑:对那些说无关紧要的人,我不是用它作为优化.我试图通过分开这个元素来更好地理解语言.

aba*_*ert 7

我们最多谈论几十纳秒,所以通常没关系.而且,即使它确实如此,事情也比最初出现时更复杂.


预绑定seen.__contains__作为seen_contains将加快速度在打电话seen.__contains__,但不如只使用(越明显,惯用语)in seen来代替.

那么,为什么这不同于seen_adds

在这种情况下seen.add(),您明确地创建并调用绑定方法,并且没有办法解决这个问题.因此,创建绑定方法一次,而不是每次...仍然通常不值得,但在极少数情况下,当您需要保存纳秒时,这是一个胜利.

在这种情况下in seen,您没有显式创建绑定方法,而只是评估运算符.在CPython的,如果seen是一个Python类的实例,将隐式创建一个绑定的方法,但如果它是一个内置的类的实例,它只是直接查找在C槽的方法和调用.因此,虽然通过一次创建绑定方法而不是一遍又一遍来节省时间,但它仍然没有浪费时间通过绑定方法而不是直接调用它来浪费时间.

当然,在不同的Python实现中 - 或者只是使用不是内置的不同类型 - 事情可能会有所不同.


如果这实际上很重要(通常不会这样),您当然应该使用平台,Python实现和您关心的类型对其进行测试.

但是,纯粹作为一个例子,我将在我的MacBook Pro上用64位python.org CPython 3.7测试它set:

In [778]: s = {1, 2, 3}
In [779]: sc = s.__contains__
In [780]: %timeit 4 in s
33.9 ns ± 0.444 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [781]: %timeit s.__contains__(4)
69.3 ns ± 0.936 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [782]: %timeit sc(4)
47.6 ns ± 0.866 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,sc我们会浪费一些浪费的时间,但不是全部.

但是使用纯Python类型:

In [787]: class Set:
     ...:     def __contains__(self, n):
     ...:         return 1 <= n < 4
In [788]: s = Set()
In [789]: sc = s.__contains__
In [790]: %timeit 4 in s
129 ns ± 5.69 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [791]: %timeit s.__contains__(4)
124 ns ± 1.14 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [792]: %timeit sc(4)
108 ns ± 1.19 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)

... 4 in s稍微一点s.__contains__(4)(因为它基本上只是调用它的包装器),并且创建绑定方法使它更快.

因此,我们得到完全相反的结果,两种不同的类型代表相同的值.

而且,这些案例中最大的差异仍然只有35ns.


作为旁注,预绑定该方法对局部人而言比全局变量更有帮助.(局部变量查找明显快于属性查找;全局变量查找仅比属性查找快一点.)在单行中更难以证明,但如果这是您的实际预期用途,您应该自己测试.


请记住,所有这一切都与CPython有关.

当我在PyPy 3.5.3/5.10.1中运行完全相同的代码时,我获得了6.39/6.29/6.31ns set和1.52/1.51/1.50ns Set.

请注意,几乎所有的细节变成了周围的其他完全相同的方式:__contains__是不是快inset,预结合它实际上会减慢速度,而不是加速起来,与非内置Set的4倍速度更快,而不是3倍速度较慢.为什么?我可以做出一些猜测,但每当我试图潜入PyPy的JIT可靠的答案,我出来了三天后,已经什么也没学到比阿明·里戈是一个18级法师多.

(还要注意,只是切换Python解释器比我们在语言中可以做的任何微优化产生的数量级更大.)

  • @wim当然可以.我不仅要解释,还要证明结果取决于这些事情.不切实际的数据集根本不重要; 第一个数据结构中的散列和第二个数据结构中的链接比较都不会与一百万个元素而不是三个元素显着不同. (3认同)