'x'가 'x' == 'x'보다 빠른 이유는 무엇입니까?
>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
또한 여러 요소가 포함된 튜플에도 사용할 수 있으며, 두 버전은 선형적으로 증가하는 것으로 보입니다.
>>> 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
이 정도만 봐서는 이제 이 제품을 사용해서in
대신 도처에==
!
David Wolever에게 말했듯이, 이것에는 눈에 보이는 것 이상의 것이 있습니다; 두 가지 방법 모두 로 보내집니다.is
; 이것을 증명하려면 , 다음의 조작을 실시합니다.
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
첫 번째는 아이덴티티에 의해 확인되기 때문에 매우 빠를 수 있습니다.
왜 한쪽이 다른 쪽보다 오래 걸리는지 알아보려면 실행을 통해 추적해 보겠습니다.
둘 다 시작해요.ceval.c
,부터COMPARE_OP
그것이 관련된 바이트 코드이기 때문에
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개만 팝됩니다).
PyObject *right = POP();
PyObject *left = TOP();
및 비교를 실행합니다.
PyObject *res = cmp_outcome(oparg, left, right);
cmp_outcome
이것은 다음과 같습니다.
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;
}
여기가 길이 갈라진 곳이야그PyCmp_IN
브런치에서는 하고 있다
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);
}
태플은 다음과 같이 정의됩니다.
static PySequenceMethods tuple_as_sequence = {
...
(objobjproc)tuplecontains, /* sq_contains */
};
PyTypeObject PyTuple_Type = {
...
&tuple_as_sequence, /* tp_as_sequence */
...
};
그래서 브랜치
if (sqm != NULL && sqm->sq_contains != NULL)
사용되다*sqm->sq_contains
이 함수는(objobjproc)tuplecontains
이 사용됩니다.
이것은 그렇다
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;
}
잠깐, 그거 아니야?PyObject_RichCompareBool
다른 나뭇가지에서 뭘 가져갔지?아니, 그건PyObject_RichCompare
.
암호 경로가 짧아서 이 두 가지 속도로 귀결될 수 있습니다.비교해 봅시다.
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;
}
...
}
의 코드 경로PyObject_RichCompareBool
거의 즉시 종료됩니다.위해서PyObject_RichCompare
,그래요.
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;
}
그Py_EnterRecursiveCall
/Py_LeaveRecursiveCall
combo는 이전 경로에서는 사용되지 않지만 글로벌 증감 후 단락되는 비교적 빠른 매크로입니다.
do_richcompare
다음 작업을 수행합니다.
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;
...
}
...
}
이것은 몇 가지 빠른 콜 체크를 수행합니다.v->ob_type->tp_richcompare
어느 것이
PyTypeObject PyUnicode_Type = {
...
PyUnicode_RichCompare, /* tp_richcompare */
...
};
어느 쪽인가 하면
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;
}
즉, 이 단축키는left == right
...하지만 한 다음에야
if (!PyUnicode_Check(left) || !PyUnicode_Check(right))
if (PyUnicode_READY(left) == -1 ||
PyUnicode_READY(right) == -1)
모든 경로에서 이와 같은 상태가 됩니다(수동으로 재귀적으로 기존의 브랜치를 인라인화, 언롤, 플루닝).
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() #
대
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() #
지금이다,PyUnicode_Check
그리고.PyUnicode_READY
는 몇 개의 필드만 체크하기 때문에 매우 저렴합니다만, 상단의 것은 작은 코드 패스이며, 함수 호출이 적고, 스위치 문이 1개뿐이며, 조금 얇을 뿐이라는 것은 분명합니다.
TL;DR:
양쪽 디스패치 대상if (left_pointer == right_pointer)
; 그들이 거기에 도달하기 위해 얼마나 많은 일을 하느냐가 차이입니다. in
덜 할 뿐입니다.
여기에는 세 가지 요인이 작용하여 이 놀라운 행동을 일으킵니다.
첫 번째:in
오퍼레이터는 숏컷을 사용하여 ID를 확인합니다(x is y
동등성을 체크하기 전에 ( )x == y
):
>>> n = float('nan')
>>> n in (n, )
True
>>> n == n
False
>>> n is n
True
둘째: Python의 문자열 인터닝으로 인해"x"
에 있다"x" in ("x", )
같게 됩니다.
>>> "x" is "x"
True
(큰 경고: 이것은 구현 고유의 동작입니다. is
문자열 비교에는 사용하지 마십시오.가끔 놀라운 답을 얻을 수 있기 때문입니다.예를 들어,"x" * 100 is "x" * 100 ==> False
)
세 번째: 비드락의 환상적인 답변에 따르면tuple.__contains__
)x in (y, )
대략 와 동등하다(y, ).__contains__(x)
는가 보다 .str.__eq__
한 번x == y
대략 와 동등하다x.__eq__(y)
가 됩니다).가 됩니다.
을 볼 수요. 왜냐하면 이 증거들이 있다라는 거죠.x in (y, )
으로 동등한 값인 리리으 is the the the the the the the the the the the the 보다 현저하게 .x == y
:
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
x in (y, )
부터는 더.★★★★★★★★★★★★★★★★★★,is
하여 " " " " 가in
체크로 "동일성 체크"를 합니다).==
)이 때문에 에 걸리는 ), 。==
태플 작성, 멤버 워킹 등의 오버헤드로 인해 동작 전체가 느려집니다.
, 「 」,a in (b, )
보다 고속인 것은a is b
:
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
?)a in (b, )
a is b or a == b
명령의 가 적어지는 것.- 가상 머신의 명령의 수가 적어집니다.a in (b, )
서 3개요.a is b or a == b
VM을 사용합니다).
Veedrac의 답변(https://stackoverflow.com/a/28889838/71522)은 특히 각 비즈니스에서 일어나는 일에 대해 훨씬 더 자세히 설명합니다.==
★★★★★★★★★★★★★★★★★」in
읽을 가치가 있습니다.
언급URL : https://stackoverflow.com/questions/28885132/why-is-x-in-x-faster-than-x-x
'IT' 카테고리의 다른 글
PHP에서 호출 함수/메서드의 이름을 얻는 방법은? (0) | 2022.11.18 |
---|---|
고정 헤더가 있는 HTML 테이블? (0) | 2022.11.18 |
Printf는 문장의 첫 글자만 표시합니다. (0) | 2022.11.18 |
SQL Chemy: 서로 다른 데이터베이스 간 결합 및 모듈 내 서로 다른 파일 사용 (0) | 2022.11.18 |
OS X의 mariadb 도커 컨테이너: 데이터베이스 디렉토리를 작성할 수 없습니다. (0) | 2022.11.18 |