>&燃气轮机&燃气轮机;timeit.timeit('x',)中的“x”)
0.04869917374131205
&燃气轮机&燃气轮机&燃气轮机;timeit.timeit('x'='x')
0.06144205736110564
也适用于具有多个元素的元组,两个版本似乎都呈线性增长:
>&燃气轮机&燃气轮机;timeit.timeit('x','y')中的'x')
0.04866674801541748
&燃气轮机&燃气轮机&燃气轮机;timeit.timeit('x'='x'或'x'='y'))
0.06565782838087131
&燃气轮机&燃气轮机&燃气轮机;timeit.timeit('y',x')中的'x'))
0.08975995576448526
&燃气轮机&燃气轮机&燃气轮机;timeit.timeit('x'='y'或'x'='y')
0.12992391047427532
基于此,我认为我应该完全开始在任何地方使用,而不是=
正如我对大卫·沃勒夫所说的,这件事的意义远远超出了人们的想象;两种方法都发送到is;你可以通过这样做来证明这一点
min(计时器(“x==x”,setup=“x='a'*1000000”)。重复(101000))
#&燃气轮机&燃气轮机&燃气轮机;0.00045456900261342525
最小值(计时器(“x==y”,setup=“x='a'*1000000;y='a'*1000000”)。重复(101000)
#&燃气轮机&燃气轮机&燃气轮机;0.5256857610074803
第一个只能如此之快,因为它通过身份进行检查
为了找出为什么一个要比另一个花费更长的时间,让我们跟踪执行过程
它们都从ceval.c开始,从COMPARE\u OP开始,因为这是所涉及的字节码
目标(比较){
PyObject*right=POP();
PyObject*left=TOP();
PyObject*res=cmp_结果(oparg,左,右);
Py_DECREF(左);
Py_DECREF(右);
设置顶部(res);
如果(res==NULL)
转到错误;
预测(如果为假,则弹出跳转);
预测(如果为真,则弹出跳转);
分派();
}
这将从堆栈中弹出值(技术上只弹出一个)
PyObject*right=POP();
PyObject*left=TOP();
并运行比较:
PyObject*res=cmp\u结果(oparg,左,右);
cmp\u结果是:
静态PyObject*
cmp_结果(int-op,PyObject*v,PyObject*w)
{
int res=0;
开关(op){
案例PyCmp_是:。。。
案例PyCmp_不是:。。。
案例PyCmp_在:
res=PySequence_包含(w,v);
如果(res<;0)
返回NULL;
打破
案例PyCmp_不在:。。。
案例PyCmp_EXC_匹配:。。。
违约:
返回PyObject_RichCompare(v,w,op);
}
v=res?Py_True:Py_False;
Py_增量(v);
返回v;
}
这就是路径分裂的地方。分支中的PyCmp_
int
PySequence_包含(PyObject*seq,PyObject*ob)
{
使用结果;
PySequenceMethods*sqm=seq->;ob\U类型->;tp\U as\U序列;
if(sqm!=NULL&;sqm->;sq_包含!=NULL)
返回(*sqm->;sq_包含)(序号,ob);
结果=_PySequence_IterSearch(seq,ob,PY_IterSearch_包含);
返回Py_SAFE_DOWNCAST(结果,Py_ssize_t,int);
}
请注意,元组定义为
静态PySequenceMethods元组作为序列={
...
(objobjproc)tuplecontains,/*sq_包含*/
};
PyTypeObject PyTuple\u类型={
...
&;元组作为序列,/*tp作为序列*/
...
};
那么分支机构呢
如果(sqm!=NULL&;sqm->;sq_包含!=NULL)
将采取和*sqm->;sq_contains,这是将采用的函数(objobjproc)tuplecontains
确实如此
静态int
tuplecontains(PyTupleObject*a,PyObject*el)
{
Py_ssize_t i;
int-cmp;
对于(i=0,cmp=0;cmp==0&;i<;Py_尺寸(a);+i)
cmp=PyObject\u RichCompareBool(el,PyTuple\u GET\u项(a,i),
Py_-EQ);
返回cmp;
}
…等等,那不是另一个分支所做的吗?不,那是PyObject\u RichCompare
那条代码路径很短,所以很可能要归结为这两条代码的速度。让我们比较一下
int
PyObject_richcomarebool(PyObject*v,PyObject*w,int op)
{
PyObject*res;
int-ok;
/*对象相同时的快速结果。
保证身份意味着平等*/
如果(v==w){
if(op==Py_EQ)
返回1;
else if(op==Py_NE)
返回0;
}
...
}
PyObject\u richcomarebool中的代码路径几乎立即终止。对于PyObject\u RichCompare,它会
PyObject*
PyObject_RichCompare(PyObject*v,PyObject*w,int op)
{
PyObject*res;
断言(Py_LT<;=op&;op<;=Py_GE);
如果(v==NULL | | w==NULL){…}
如果(Py_EnterRecursiveCall(“比较”))
返回NULL;
res=do_richcompare(v,w,op);
Py_LeaveRecursiveCall();
返回res;
}
前面的路径中没有使用Py\u EnterRecursiveCall/Py\u LeaveRecursiveCall组合,但这些是相对快速的宏,在某些全局变量递增或递减后会短路
do_richcomparedo:
静态PyObject*
do_richcompare(PyObject*v,PyObject*w,int op)
{
richcmpfunc f;
PyObject*res;
int checked_reverse_op=0;
如果(v->;ob_type!=w->;ob_type&;…){…}
if((f=v->;ob\u type->;tp\u richcompare)!=NULL){
res=(*f)(v,w,op);
如果(res!=Py_未实现)
返回res;
...
}
...
}
这将执行一些快速检查以调用v->;ob_类型->;tp_richcompare哪个是
PyTypeObject PyUnicode\u Type={
...
PyUnicode_RichCompare,/*tp_RichCompare*/
...
};
哪一个
PyObject*
PyUnicode_RichCompare(PyObject*左,PyObject*右,整数运算)
{
int结果;
PyObject*v;
如果(!PyUnicode|u Check(左)| |!PyUnicode|u Check(右))
Py_返回未执行;
如果(PyUnicode_就绪(左)=-1||
PyUnicode_就绪(右)=-1)
返回NULL;
如果(左==右){
开关(op){
案例Py_EQ:
案例:
案例:
/*字符串等于它本身*/
v=Py_真;
打破
案例:
案例Py_LT:
案例Py\u GT:
v=Py_假;
打破
违约:
...
}
}
如果(…){…}
否则{…}
Py_增量(v);
返回v;
}
也就是说,这个快捷键位于left==right。。。但只有这样做之后
如果(!PyUnicode|u Check(左)| |!PyUnicode|u Check(右))
如果(PyUnicode_就绪(左)=-1||
PyUnicode_就绪(右)=-1)
所有路径看起来都是这样的(手动递归内联、展开和修剪已知分支)
POP()#堆栈内容
TOP()#
#
案例PyCmp_IN:#运行时派送
#
平方米!=空#发送到内置op
平方米->;sq_包含!=空的#
*平方米->;sq_包含#
#
cmp==0#在循环中进行比较
我<;Py_尺寸(a)#
v==w#
op==Py_EQ#
++我#
cmp==0#
#
res<;0#转换为Python空间
物件?Py_True:Py_False#
Py_增量(v)#
#
Py_DECREF(左)#堆叠材料
Py_DECREF(右)#
设置顶部(分辨率)#
res==NULL#
调度()#
vs
POP()#堆栈内容
TOP()#
#
默认值:#运行时派送
#
Py_LT<;=op#检查操作
op<;=佩格#
v==NULL#
w==NULL#
Py_EnterRecursiveCall(…)#递归检查
#
v->;ob_类型!=w->;ob#U类型#更多操作检查
f=v->;ob_类型->;tp#调度至内置op
f!=空的#
#
!PyUnicode_检查(左)#…更多检查
!PYU校验(右))#
PyUnicode_就绪(左)=-1#
PyUnicode_就绪(右)=-1#
左==右#最后,做比较
案例Py_EQ:#立即短路
Py_增量(v)#
#
res!=Py_未实施#
#
Py_LeaveRecursiveCall()#递归检查
#
Py_DECREF(左)#堆叠材料
Py_DECREF(右)#
设置顶部(分辨率)#
res==NULL#
调度()#
现在,PyUnicode\u Check和PyUnicode\u READY非常便宜,因为它们只检查两个字段,但是应该很明显,上面的一个是较小的代码路径,它有较少的函数调用,只有一个开关
声明,只是有点瘦
TL;博士:
如果(左指针==右指针),则两个命令都发送到if(左指针==右指针);不同之处在于他们为达到目标做了多少工作在中只是做得更少