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

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

seen = set()
seen_add = seen.add

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

seen = set()
seen_add = seen.add
in_seen = seen.__contains__
for item in iterable:
    in_seen(item)

VS

seen = set()
seen_add = seen.add
in_seen = seen.__contains__  # make identical in beginning
for item in iterable:
    item in seen

因此,如果我正确地读取dis的输出,问题归结为“x是y比func(x)快吗?”

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

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

预绑定看到.__包含_,因为看到_contains会比调用看到.__ contains__更快,但不会像看到的那样只使用(更明显和惯用).

那么,为什么这与seen_adds不同?

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

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

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

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

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

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)

正如预期的那样,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)

… s中的4比s慢一点___包含__(4)(因为它基本上只是一个完全调用的包装器),并且创建绑定方法使它更快.

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

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

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

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

当我在PyPy 3.5.3 / 5.10.1中运行完全相同的代码时,设置为6.39 / 6.29 / 6.31ns,Set为1.52 / 1.51 / 1.50ns.

请注意,几乎所有的细节都完全相反:__contains__比set更快,预绑定它实际上减慢了速度而不是加速它们,非内置集快4倍而不是3倍慢.为什么?我可以做一些猜测,但每当我试图深入PyPy的JIT获得可靠的答案时,我在三天后出来,只学到了Armin Rigo是一个18级巫师.

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

点赞