Như tôi đã đề cập với David Wolever, có nhiều thứ hơn là bắt mắt; cả hai phương thức gửi đến is
; bạn có thể chứng minh điều này bằng cách làm
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
Việc đầu tiên chỉ có thể nhanh như vậy bởi vì nó kiểm tra theo danh tính.
Để tìm hiểu lý do tại sao một người sẽ mất nhiều thời gian hơn người khác, hãy theo dõi thông qua thực hiện.
Cả hai đều bắt đầu ceval.c
, COMPARE_OP
vì đó là mã byte liên quan
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();
}
Điều này bật các giá trị từ ngăn xếp (về mặt kỹ thuật nó chỉ bật một)
PyObject *right = POP();
PyObject *left = TOP();
và chạy so sánh:
PyObject *res = cmp_outcome(oparg, left, right);
cmp_outcome
có phải đây là:
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;
}
Đây là nơi các đường dẫn phân chia. Các PyCmp_IN
chi nhánh không
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);
}
Lưu ý rằng một tuple được định nghĩa là
static PySequenceMethods tuple_as_sequence = {
...
(objobjproc)tuplecontains, /* sq_contains */
};
PyTypeObject PyTuple_Type = {
...
&tuple_as_sequence, /* tp_as_sequence */
...
};
Chi nhánh
if (sqm != NULL && sqm->sq_contains != NULL)
sẽ được thực hiện và *sqm->sq_contains
, đó là chức năng (objobjproc)tuplecontains
, sẽ được thực hiện.
Cái này không
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;
}
... Đợi đã, đó không phải là PyObject_RichCompareBool
những gì các chi nhánh khác đã lấy? Không, đó là PyObject_RichCompare
.
Đường dẫn mã đó ngắn nên có khả năng giảm tốc độ của hai thứ này. Hãy so sánh.
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;
}
...
}
Đường dẫn mã trong PyObject_RichCompareBool
khá nhiều ngay lập tức chấm dứt. Đối với PyObject_RichCompare
nó
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;
}
Các Py_EnterRecursiveCall
/ Py_LeaveRecursiveCall
kết hợp không được thực hiện trong đường dẫn trước, nhưng đây là những macro tương đối nhanh chóng mà sẽ ngắn mạch sau khi tăng và giảm một số globals.
do_richcompare
làm:
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;
...
}
...
}
Đây là một số kiểm tra nhanh để gọi v->ob_type->tp_richcompare
đó là
PyTypeObject PyUnicode_Type = {
...
PyUnicode_RichCompare, /* tp_richcompare */
...
};
cái nào
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;
}
Cụ thể, phím tắt này trên left == right
... nhưng chỉ sau khi thực hiện
if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)
Tất cả trong tất cả các đường dẫn sau đó trông giống như thế này (thủ công đệ quy nội tuyến, không kiểm soát và cắt tỉa các nhánh đã biết)
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() #
đấu với
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() #
Bây giờ, PyUnicode_Check
và PyUnicode_READY
khá rẻ vì họ chỉ kiểm tra một vài trường, nhưng rõ ràng là cái trên cùng là một đường dẫn mã nhỏ hơn, nó có ít lệnh gọi hàm hơn, chỉ có một câu lệnh chuyển đổi và chỉ mỏng hơn một chút.
TL; DR:
Cả phái đi if (left_pointer == right_pointer)
; sự khác biệt chỉ là có bao nhiêu công việc họ làm để đạt được điều đó. in
chỉ làm ít thôi
in
ở mọi nơi thay vì==
. Đó là một tối ưu hóa sớm gây hại cho khả năng đọc.