Seperti yang saya sebutkan kepada David Wolever, ada lebih dari ini yang bisa dilihat; kedua metode dikirim ke is
; Anda dapat membuktikan ini dengan melakukan
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
Yang pertama hanya bisa sangat cepat karena memeriksa dengan identitas.
Untuk mengetahui mengapa yang satu lebih lama dari yang lain, mari kita telusuri eksekusi.
Mereka berdua mulai ceval.c
, COMPARE_OP
sejak itu adalah bytecode yang terlibat
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();
}
Ini muncul nilai dari tumpukan (secara teknis hanya muncul satu)
PyObject *right = POP();
PyObject *left = TOP();
dan menjalankan pembandingan:
PyObject *res = cmp_outcome(oparg, left, right);
cmp_outcome
Apakah ini:
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;
}
Di sinilah jalur dibagi. The PyCmp_IN
cabang tidak
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);
}
Perhatikan bahwa tuple didefinisikan sebagai
static PySequenceMethods tuple_as_sequence = {
...
(objobjproc)tuplecontains, /* sq_contains */
};
PyTypeObject PyTuple_Type = {
...
&tuple_as_sequence, /* tp_as_sequence */
...
};
Jadi rantingnya
if (sqm != NULL && sqm->sq_contains != NULL)
akan diambil dan *sqm->sq_contains
, yang merupakan fungsinya (objobjproc)tuplecontains
, akan diambil.
Ini tidak
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;
}
... Tunggu, bukankah itu PyObject_RichCompareBool
yang diambil cabang lain? Tidak, itu tadi PyObject_RichCompare
.
Jalur kode itu pendek sehingga kemungkinan hanya sampai pada kecepatan keduanya. Mari kita bandingkan.
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;
}
...
}
Jalur kode di PyObject_RichCompareBool
hampir segera berakhir. Untuk PyObject_RichCompare
itu
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 tidak diambil di jalur sebelumnya, tetapi ini adalah makro yang relatif cepat yang akan pendek-sirkuit setelah incrementing dan decrementing beberapa GLOBALS.
do_richcompare
tidak:
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;
...
}
...
}
Ini melakukan beberapa pemeriksaan cepat untuk memanggil v->ob_type->tp_richcompare
yang mana
PyTypeObject PyUnicode_Type = {
...
PyUnicode_RichCompare, /* tp_richcompare */
...
};
yang mana
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;
}
Yaitu, pintasan ini di left == right
... tetapi hanya setelah dilakukan
if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)
Semua dalam semua jalur kemudian terlihat seperti ini (secara manual secara inuratif, membuka gulungan dan memangkas cabang yang dikenal)
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() #
Sekarang, PyUnicode_Check
dan PyUnicode_READY
cukup murah karena mereka hanya memeriksa beberapa bidang, tetapi harus jelas bahwa yang paling atas adalah jalur kode yang lebih kecil, ia memiliki lebih sedikit pemanggilan fungsi, hanya satu pergantian pernyataan dan hanya sedikit lebih tipis.
TL; DR:
Keduanya mengirim ke if (left_pointer == right_pointer)
; perbedaannya hanyalah seberapa banyak pekerjaan yang mereka lakukan untuk sampai ke sana. in
hanya kurang.
in
mana - mana==
. Ini adalah pengoptimalan prematur yang merusak keterbacaan.