因此,在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)
vs
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)
快吗?”
编辑:对于那些说没关系的人,我没有将其用作优化。我试图通过将此元素分开来更好地理解语言。
答案 0 :(得分:7)
我们最多只能说几十纳秒,所以通常没关系。而且,即使发生了,事情也比第一次出现要复杂得多。
将seen.__contains__
预先绑定为seen_contains
可以比调用seen.__contains__
更快,但是不仅仅使用{更明显和惯用的in seen
}。
那么,为什么这与seen_adds
不同?
对于seen.add()
,您将显式创建并调用一个绑定方法,没有办法解决。因此,一次创建绑定方法,而不是每次创建绑定方法,通常还是不值得的,但是在极少数情况下,需要节省纳秒的时间,这是一个胜利。
对于in seen
,您不是显式创建绑定方法,而是在评估运算符。在CPython中,如果seen
是Python类的实例,则将隐式创建绑定方法-但是,如果它是内置类的实例,则将直接查找该方法在C插槽中并调用它。因此,尽管通过一次而不是一遍又一次地创建绑定方法来节省时间,但它仍然不如您浪费时间通过绑定方法而不是直接调用C函数来浪费时间。
当然,在不同的Python实现中-或只是具有非内置的其他类型-情况可能会有所不同。
如果这实际上很重要(通常不重要),那么您当然应该使用平台,Python实现和您关心的类型对其进行测试。
但是,仅作为示例,我将使用set
在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)
…4 in s
比s.__contains__(4)
稍慢(emem)(因为它基本上只是调用它的包装器),并且创建bound方法会使它更快。
因此,我们得到了两种完全相反的结果,它们代表相同的值。
同样,在这些情况下,最大的区别仍然只有35ns。
请注意,预绑定方法对本地人的帮助要比对全局人的帮助大得多。 (局部变量查找比属性查找快得多;全局变量查找仅比属性查找快一点。)用单行代码很难证明这一点,但是您应该自己测试一下这是否是您的实际用途。 / p>
请记住,所有这些仅与CPython有关。
当我在PyPy 3.5.3 / 5.10.1中运行完全相同的代码时,对于set
,我得到6.39 / 6.29 / 6.31ns;对于Set
,我得到1.52 / 1.51 / 1.50ns。
请注意,几乎所有细节都完全相反:__contains__
的{{1}}比in
快,预绑定实际上会减慢速度而不是加快速度而不是内置set
快4倍而不是慢3倍。为什么?我可以做出一些猜测,但是每当我尝试深入PyPy的JIT以获得可靠的答案时,我三天后就出来了,他所了解的不过是Armin Rigo是18级向导。
(还要注意,仅切换Python解释器所产生的差异就比我们在该语言中所能进行的任何微优化都要大一个数量级。)
答案 1 :(得分:-1)
in
似乎更快。猜测COMPARE_OP
比CALL_FUNCTION
更有效率,因为它知道它有多少个参数。
haugh@~$ python3 -m timeit -s "l = {1}" "2 in l"
10000000 loops, best of 3: 0.029 usec per loop
haugh@~$ python3 -m timeit -s "l = {1}" "l.__contains__(2)"
10000000 loops, best of 3: 0.0612 usec per loop
haugh@~$ python3 -m timeit -s "l = {1}; isin=l.__contains__" "isin(2)"
10000000 loops, best of 3: 0.0301 usec per loop