Why is 'x' in ('x',) faster than 'x' == 'x'?
1 2 3 4 | >>> timeit.timeit("'x' in ('x',)") 0.04869917374131205 >>> timeit.timeit("'x' == 'x'") 0.06144205736110564 |
同样适用于具有多个元素的元组,两个版本似乎都呈线性增长:
1 2 3 4 5 6 7 8 | >>> timeit.timeit("'x' in ('x', 'y')") 0.04866674801541748 >>> timeit.timeit("'x' == 'x' or 'x' == 'y'") 0.06565782838087131 >>> timeit.timeit("'x' in ('y', 'x')") 0.08975995576448526 >>> timeit.timeit("'x' == 'y' or 'x' == 'y'") 0.12992391047427532 |
号
基于此,我认为我应该完全开始在任何地方使用
正如我对大卫·沃尔夫所提到的,这不仅仅是一个问题;这两种方法都发送到
1 2 3 4 5 | min(Timer("x == x", setup="x = 'a' * 1000000").repeat(10, 10000)) #>>> 0.00045456900261342525 min(Timer("x == y", setup="x = 'a' * 1000000; y = 'a' * 1000000").repeat(10, 10000)) #>>> 0.5256857610074803 |
第一个只能这么快,因为它通过身份进行检查。
为了找出其中一个比另一个花费更长时间的原因,让我们通过执行进行跟踪。
它们都是从
1 2 3 4 5 6 7 8 9 10 11 12 13 | TARGET(COMPARE_OP) { PyObject *right = POP(); PyObject *left = TOP(); PyObject *res = cmp_outcome(oparg, left, right); Py_DECREF(left); Py_DECREF(right); SET_TOP(res); if (res == NULL) goto error; PREDICT(POP_JUMP_IF_FALSE); PREDICT(POP_JUMP_IF_TRUE); DISPATCH(); } |
。
这将从堆栈中弹出值(从技术上讲,它只弹出一个值)
1 2 | PyObject *right = POP(); PyObject *left = TOP(); |
。
运行比较:
1 | PyObject *res = cmp_outcome(oparg, left, right); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | static PyObject * cmp_outcome(int op, PyObject *v, PyObject *w) { int res = 0; switch (op) { case PyCmp_IS: ... case PyCmp_IS_NOT: ... case PyCmp_IN: res = PySequence_Contains(w, v); if (res < 0) return NULL; break; case PyCmp_NOT_IN: ... case PyCmp_EXC_MATCH: ... default: return PyObject_RichCompare(v, w, op); } v = res ? Py_True : Py_False; Py_INCREF(v); return v; } |
。
这就是路径分割的地方。
1 2 3 4 5 6 7 8 9 10 | int PySequence_Contains(PyObject *seq, PyObject *ob) { Py_ssize_t result; PySequenceMethods *sqm = seq->ob_type->tp_as_sequence; if (sqm != NULL && sqm->sq_contains != NULL) return (*sqm->sq_contains)(seq, ob); result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS); return Py_SAFE_DOWNCAST(result, Py_ssize_t, int); } |
注意,元组定义为
1 2 3 4 5 6 7 8 9 10 | static PySequenceMethods tuple_as_sequence = { ... (objobjproc)tuplecontains, /* sq_contains */ }; PyTypeObject PyTuple_Type = { ... &tuple_as_sequence, /* tp_as_sequence */ ... }; |
。
所以分支机构
1 | if (sqm != NULL && sqm->sq_contains != NULL) |
号
将采取和
是的。
1 2 3 4 5 6 7 8 9 10 11 | static int tuplecontains(PyTupleObject *a, PyObject *el) { Py_ssize_t i; int cmp; for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i) cmp = PyObject_RichCompareBool(el, PyTuple_GET_ITEM(a, i), Py_EQ); return cmp; } |
号
…等等,那不是另一个分支机构所采取的措施吗?不,那是
这个代码路径很短,所以它很可能会下降到这两个的速度。让我们比较一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int PyObject_RichCompareBool(PyObject *v, PyObject *w, int op) { PyObject *res; int ok; /* Quick result when objects are the same. Guarantees that identity implies equality. */ if (v == w) { if (op == Py_EQ) return 1; else if (op == Py_NE) return 0; } ... } |
号
1 2 3 4 5 6 7 8 9 10 11 12 13 | PyObject * PyObject_RichCompare(PyObject *v, PyObject *w, int op) { PyObject *res; assert(Py_LT <= op && op <= Py_GE); if (v == NULL || w == NULL) { ... } if (Py_EnterRecursiveCall(" in comparison")) return NULL; res = do_richcompare(v, w, op); Py_LeaveRecursiveCall(); return res; } |
号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static PyObject * do_richcompare(PyObject *v, PyObject *w, int op) { richcmpfunc f; PyObject *res; int checked_reverse_op = 0; if (v->ob_type != w->ob_type && ...) { ... } if ((f = v->ob_type->tp_richcompare) != NULL) { res = (*f)(v, w, op); if (res != Py_NotImplemented) return res; ... } ... } |
号
这会快速检查一下,叫
1 2 3 4 5 | PyTypeObject PyUnicode_Type = { ... PyUnicode_RichCompare, /* tp_richcompare */ ... }; |
号
哪一个可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | PyObject * PyUnicode_RichCompare(PyObject *left, PyObject *right, int op) { int result; PyObject *v; if (!PyUnicode_Check(left) || !PyUnicode_Check(right)) Py_RETURN_NOTIMPLEMENTED; if (PyUnicode_READY(left) == -1 || PyUnicode_READY(right) == -1) return NULL; if (left == right) { switch (op) { case Py_EQ: case Py_LE: case Py_GE: /* a string is equal to itself */ v = Py_True; break; case Py_NE: case Py_LT: case Py_GT: v = Py_False; break; default: ... } } else if (...) { ... } else { ...} Py_INCREF(v); return v; } |
号
也就是说,
1 2 3 4 | if (!PyUnicode_Check(left) || !PyUnicode_Check(right)) if (PyUnicode_READY(left) == -1 || PyUnicode_READY(right) == -1) |
号
所有路径都是这样的(手动递归地内联、展开和修剪已知的分支)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | POP() # Stack stuff TOP() # # case PyCmp_IN: # Dispatch on operation # sqm != NULL # Dispatch to builtin op sqm->sq_contains != NULL # *sqm->sq_contains # # cmp == 0 # Do comparison in loop i < Py_SIZE(a) # v == w # op == Py_EQ # ++i # cmp == 0 # # res < 0 # Convert to Python-space res ? Py_True : Py_False # Py_INCREF(v) # # Py_DECREF(left) # Stack stuff Py_DECREF(right) # SET_TOP(res) # res == NULL # DISPATCH() # |
号
VS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | POP() # Stack stuff TOP() # # default: # Dispatch on operation # Py_LT <= op # Checking operation op <= Py_GE # v == NULL # w == NULL # Py_EnterRecursiveCall(...) # Recursive check # v->ob_type != w->ob_type # More operation checks f = v->ob_type->tp_richcompare # Dispatch to builtin op f != NULL # # !PyUnicode_Check(left) # ...More checks !PyUnicode_Check(right)) # PyUnicode_READY(left) == -1 # PyUnicode_READY(right) == -1 # left == right # Finally, doing comparison case Py_EQ: # Immediately short circuit Py_INCREF(v); # # res != Py_NotImplemented # # Py_LeaveRecursiveCall() # Recursive check # Py_DECREF(left) # Stack stuff Py_DECREF(right) # SET_TOP(res) # res == NULL # DISPATCH() # |
现在,
两者都发送到
这里有三个因素在起作用,综合起来,产生了这种令人惊讶的行为。
第一:
1 2 3 4 5 6 7 | >>> n = float('nan') >>> n in (n, ) True >>> n == n False >>> n is n True |
第二:由于python的字符串interning,
1 2 | >>>"x" is"x" True |
号
(大警告:这是特定于实现的行为!不应使用
第三:正如Veedrac神奇的回答所详述的那样,
你可以看到这方面的证据,因为
1 2 3 4 5 6 7 8 9 10 11 | In [18]: %timeit 'x' in ('x', ) 10000000 loops, best of 3: 65.2 ns per loop In [19]: %timeit 'x' == 'x' 10000000 loops, best of 3: 68 ns per loop In [20]: %timeit 'x' in ('y', ) 10000000 loops, best of 3: 73.4 ns per loop In [21]: %timeit 'x' == 'y' 10000000 loops, best of 3: 56.2 ns per loop |
还要注意,只有当
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | In [48]: a = 1 In [49]: b = 2 In [50]: %timeit a is a or a == a 10000000 loops, best of 3: 95.1 ns per loop In [51]: %timeit a in (a, ) 10000000 loops, best of 3: 140 ns per loop In [52]: %timeit a is b or a == b 10000000 loops, best of 3: 177 ns per loop In [53]: %timeit a in (b, ) 10000000 loops, best of 3: 169 ns per loop |
。
(为什么
Veedrac的答案-https://stackoverflow.com/a/28889838/71522-详细介绍了在