Question:

>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564

Also works for multiple options, both seem to grow linearly:

>>> 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

Based on this, I think I should start using in everywhere
instead of == !!

Answer:

As I mentioned to David Wolever, there's more to this than meets the eye; both methods dispatch to is;
you can prove this by doing

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

The first can only be so fast because it checks by identity.

To find out why one would take longer than the other, let's trace through execution.

They both start in ceval.c,
from COMPARE_OP since
that is the bytecode involved

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();
}

This pops the values from the stack (technically it only pops one)

PyObject *right = POP();
PyObject *left = TOP();

and runs the compare:

PyObject *res = cmp_outcome(oparg, left, right);

cmp_outcome is
this:

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;
}

This is where the paths split. The PyCmp_IN branch
does

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);
}

Note that a tuple is defined as

static PySequenceMethods tuple_as_sequence = {
...
(objobjproc)tuplecontains, /* sq_contains */
}; PyTypeObject PyTuple_Type = {
...
&tuple_as_sequence, /* tp_as_sequence */
...
};

So the branch

if (sqm != NULL && sqm->sq_contains != NULL)

will be taken and *sqm->sq_contains,
which is the function (objobjproc)tuplecontains,
will be taken.

This does

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;
}

...Wait, wasn't that PyObject_RichCompareBool what
the other branch took? Nope, that was PyObject_RichCompare.

That code path was short so it likely just comes down to the speed of these two. Let's compare.

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;
} ...
}

The code path in PyObject_RichCompareBool pretty
much immediately terminates. For PyObject_RichCompare,
it does

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;
}

The Py_EnterRecursiveCall/Py_LeaveRecursiveCall combo
are not taken in the previous path, but these are relatively quick macros that'll short-circuit after incrementing and decrementing some globals.

do_richcompare does:

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;
...
}
...
}

This does some quick checks to call v->ob_type->tp_richcompare which
is

PyTypeObject PyUnicode_Type = {
...
PyUnicode_RichCompare, /* tp_richcompare */
...
};

which does

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;
}

Namely, this shortcuts on left
== right
... but only after doing

    if (!PyUnicode_Check(left) || !PyUnicode_Check(right))

    if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)

All in all the paths then look something like this (manually recursively inlining, unrolling and pruning known branches)

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

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() #

Now, PyUnicode_Check and PyUnicode_READY are
pretty cheap since they only check a couple of fields, but it should be obvious that the top one is a smaller code path, it has fewer function calls, only one switch statement and is just a bit thinner.

TL;DR:

Both dispatch to if
(left_pointer == right_pointer)
; the difference is just how much work they do to get there. in just
does less.

Why is 'x' in ('x',) faster than 'x' == 'x'?的更多相关文章

  1. faster r-cnn 在CPU配置下训练自己的数据

    因为没有GPU,所以在CPU下训练自己的数据,中间遇到了各种各样的坑,还好没有放弃,特以此文记录此过程. 1.在CPU下配置faster r-cnn,参考博客:http://blog.csdn.net ...

  2. r-cnn学习系列(三):从r-cnn到faster r-cnn

    把r-cnn系列总结下,让整个流程更清晰. 整个系列是从r-cnn至spp-net到fast r-cnn再到faster r-cnn.  RCNN 输入图像,使用selective search来构造 ...

  3. faster with MyISAM tables than with InnoDB or NDB tables

    http://dev.mysql.com/doc/refman/5.7/en/partitioning-limitations.html Performance considerations.  So ...

  4. situations where MyISAM will be faster than InnoDB

    http://www.tocker.ca/categories/myisam Converting MyISAM to InnoDB and a lesson on variance I'm abou ...

  5. Faster RNNLM (HS/NCE) toolkit

    https://github.com/kjw0612/awesome-rnn Faster Recurrent Neural Network Language Modeling Toolkit wit ...

  6. Faster R-CNN CPU环境搭建

    操作系统: bigtop@bigtop-SdcOS-Hypervisor:~/py-faster-rcnn/tools$ cat /etc/issue Ubuntu LTS \n \l Python版 ...

  7. Why is processing a sorted array faster than an unsorted array?

    这是我在逛 Stack Overflow 时遇见的一个高分问题:Why is processing a sorted array faster than an unsorted array?,我觉得这 ...

  8. Introducing the Accelerated Mobile Pages Project, for a faster, open mobile web

    https://googleblog.blogspot.com/2015/10/introducing-accelerated-mobile-pages.html October 7, 2015 Sm ...

  9. 论文阅读之:Is Faster R-CNN Doing Well for Pedestrian Detection?

    Is Faster R-CNN Doing Well for Pedestrian Detection? ECCV 2016   Liliang Zhang & Kaiming He 原文链接 ...

  10. 如何才能将Faster R-CNN训练起来?

    如何才能将Faster R-CNN训练起来? 首先进入 Faster RCNN 的官网啦,即:https://github.com/rbgirshick/py-faster-rcnn#installa ...

随机推荐

  1. Build.gradle的详细配置说明

    转自:http://blog.csdn.net/u012246458/article/details/51722624 apply plugin: 'com.android.application'/ ...

  2. IDEA运行android项目一直是同一个apk

    1.IDEA运行android项目时不像eclipse和android studio那样直接运行,IDEA需要设置Artifacts,这样每次运行的时候它才能重新编译,以下附上步骤! 这样就可以了.

  3. 171. Excel表列序号

    题目:给定一个Excel表格中的列名称,返回其相应的列序号. 例如, A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> ...

  4. [.net core学习] .net core中的Rijndael取代方法

    The difference (in .NET) between Rijndael and AES is that Rijndael allows the block size to change, ...

  5. 准备情人节礼物比写代码难?来看看IT直男给女友们的礼物

    今天是情人节,据说IT直男在每个这样弥漫着恋爱气息的日子里都能把礼物送成"辣眼睛"现场,为了反(zheng)驳(shi)这个观点,小编特意走访了网易云的架构师.工程师.产品经理.程 ...

  6. Jquery - 添加属性、添加class、添加Css

    一.设置属性: 方式一  jQuery 代码: $("img").attr({ src: "test.jpg", alt: "Test Image&q ...

  7. Linux pwn入门教程——格式化字符串漏洞

    本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...

  8. Http Header 之 Requests Header 和 Responses Header

    在开发中,经常会遇到对网络请求添加相应的头信息,下面我们梳理一下Http Header相关的内容. 一.Requests Header Header 解释 示例 Accept 指定客户端能够接收的内容 ...

  9. 火狐浏览器安装 Modify Headers 插件

    一.火狐浏览器插件安装 这里以火狐浏览器的Modify Headers插件安装为例,展示火狐插件的安装: 1.打开火狐浏览器,右上角选择“附加组件” 2.搜索Modify Headers插件 3.安装 ...

  10. 分布式任务调度系统xxl-job源码探究(二、服务中心)

    接下来看下服务端代码 服务端源码 服务端通过管理quartz定时任务组件,分发任务 先从入口看起,由web.xml进入,可以看出,自己编写的代码从applicationcontext-xxl-job- ...