因此在 itertools 配方部分,他们有一段代码如下所示:
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)
对比
seen = set()
seen_add = seen.add
in_seen = seen.__contains__ # make identical in beginning
for item in iterable:
item in seen
因此,如果我正确地读取了 dis 的输出,问题就变成了“x in y
是否比 func(x)
快?”
编辑:对于那些说这无关紧要的人,我没有将其用作优化。我试图通过将这个元素分开来更好地理解语言。
最佳答案
我们说的最多是几十纳秒,所以通常这并不重要。而且,即使是这样,事情也比表面上看起来更复杂。
将 seen.__contains__
预绑定(bind)为 seen_contains
会比调用 seen.__contains__
更快,但不如仅使用 (更明显和惯用的)in seen
而不是。
那么,为什么这与 seen_adds
不同?
在 seen.add()
的情况下,您正在显式创建和调用绑定(bind)方法,没有办法解决这个问题。因此,创建绑定(bind)方法一次,而不是每次……通常仍然不值得,但在极少数情况下,当您需要节省纳秒时,这是一个胜利。
在 in seen
的情况下,您并未显式创建绑定(bind)方法,您只是在评估运算符。在 CPython 中,如果 seen
是 Python 类的一个实例,那将隐式创建一个绑定(bind)方法——但如果它是一个内置类的实例,它只会直接查找在 C 插槽中调用方法并调用它。因此,虽然您通过一次创建绑定(bind)方法而不是一遍又一遍地创建绑定(bind)方法来节省时间,但它仍然不如通过绑定(bind)方法调用 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)
正如预期的那样,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)
… 4 in s
比 s.__contains__(4)
稍微慢(因为它基本上只是一个围绕调用的包装器) ,并创建绑定(bind)方法使其更快。
因此,对于表示相同值的两种不同类型,我们得到了完全相反的结果。
同样,这些情况中最大的差异仍然只有 35ns。
作为旁注,预绑定(bind)方法对局部变量的帮助比对全局变量的帮助更大。 (局部变量查找比属性查找快得多;全局变量查找只比属性查找快一点点。)这在单行代码中很难证明,但如果这是您的实际预期用途,您应该自己测试。
请记住,所有这些都只适用于 CPython。
当我在 PyPy 3.5.3/5.10.1 中运行完全相同的代码时,set
得到 6.39/6.29/6.31ns,Set< 得到 1.52/1.51/1.50ns/
.
请注意,几乎所有细节都完全相反:__contains__
比 in
快于 set
,预绑定(bind)它实际上减慢了速度而不是加快了速度,非内置 Set
快了 4 倍而不是慢了 3 倍。为什么?我可以做出一些猜测,但每当我尝试深入研究 PyPy 的 JIT 以获得可靠的答案时,三天后我出来时除了了解到 Armin Rigo 是一个 18 级巫师之外什么都没有。
(另请注意,仅切换 Python 解释器所产生的差异比我们可以在该语言中进行的任何微优化都要大一个数量级。)
关于python - `in` 比 `__contains__` 有多少优化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52010120/