Python 코드가 함수에서 더 빠르게 실행되는 이유는 무엇입니까?
def main():
for i in xrange(10**8):
pass
main()
Python의이 코드는 다음에서 실행됩니다 (참고 : 타이밍은 Linux의 BASH에서 시간 함수로 수행됩니다.)
real 0m1.841s
user 0m1.828s
sys 0m0.012s
그러나 for 루프가 함수 내에 위치하지 않으면
for i in xrange(10**8):
pass
그런 다음 훨씬 더 오랜 시간 동안 실행됩니다.
real 0m4.543s
user 0m4.524s
sys 0m0.012s
왜 이런거야?
전역 변수보다 지역 변수를 저장하는 것이 더 빠른 이유를 물을 수 있습니다 . 이것은 CPython 구현 세부 사항입니다.
CPython은 인터프리터가 실행하는 바이트 코드로 컴파일됩니다. 함수를 컴파일 할 때, 로컬 변수는 (고정 된 크기 어레이에 저장되어 있지dict
) 및 변수 이름은 인덱스에 할당된다. 함수에 지역 변수를 동적으로 추가 할 수 없기 때문에 가능합니다. 그런 다음 지역 변수를 검색하는 것은 말 그대로 목록에 대한 포인터 조회와 PyObject
사소한 참조 횟수 증가입니다 .
이것을 해시 등을 포함하는 LOAD_GLOBAL
진정한 dict
검색 인 글로벌 조회 ( )와 대조하십시오 . 덧붙여서, 이것이 당신 global i
이 전역 적이기를 원하는지 지정해야하는 이유입니다 : 만약 당신이 범위 내의 변수에 할당한다면, 컴파일러는 STORE_FAST
당신이 그렇게하지 않는 한 접근을 위해 s를 발행 할 것입니다.
그건 그렇고, 글로벌 조회는 여전히 꽤 최적화되어 있습니다. 속성 조회 foo.bar
는 정말 느린 것입니다!
다음은 지역 변수 효율성에 대한 작은 그림 입니다.
함수 내에서 바이트 코드는
2 0 SETUP_LOOP 20 (to 23)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_CONST 3 (100000000)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 6 (to 22)
16 STORE_FAST 0 (i)
3 19 JUMP_ABSOLUTE 13
>> 22 POP_BLOCK
>> 23 LOAD_CONST 0 (None)
26 RETURN_VALUE
최상위 수준에서 바이트 코드는
1 0 SETUP_LOOP 20 (to 23)
3 LOAD_NAME 0 (xrange)
6 LOAD_CONST 3 (100000000)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 6 (to 22)
16 STORE_NAME 1 (i)
2 19 JUMP_ABSOLUTE 13
>> 22 POP_BLOCK
>> 23 LOAD_CONST 2 (None)
26 RETURN_VALUE
차이점은 STORE_FAST
더 빠르다는 것입니다 (!) STORE_NAME
. 이것은 함수에서는 i
로컬이지만 최상위에서는 전역이기 때문입니다.
바이트 코드를 조사하려면 dis
모듈을 사용하십시오 . 함수를 직접 분해 할 수 있었지만 최상위 코드를 분해하려면 compile
builtin 을 사용해야했습니다 .
로컬 / 글로벌 변수 저장 시간 외에도 opcode 예측 은 함수를 더 빠르게 만듭니다.
As the other answers explain, the function uses the STORE_FAST
opcode in the loop. Here's the bytecode for the function's loop:
>> 13 FOR_ITER 6 (to 22) # get next value from iterator
16 STORE_FAST 0 (x) # set local variable
19 JUMP_ABSOLUTE 13 # back to FOR_ITER
Normally when a program is run, Python executes each opcode one after the other, keeping track of the a stack and preforming other checks on the stack frame after each opcode is executed. Opcode prediction means that in certain cases Python is able to jump directly to the next opcode, thus avoiding some of this overhead.
In this case, every time Python sees FOR_ITER
(the top of the loop), it will "predict" that STORE_FAST
is the next opcode it has to execute. Python then peeks at the next opcode and, if the prediction was correct, it jumps straight to STORE_FAST
. This has the effect of squeezing the two opcodes into a single opcode.
On the other hand, the STORE_NAME
opcode is used in the loop at the global level. Python does *not* make similar predictions when it sees this opcode. Instead, it must go back to the top of the evaluation-loop which has obvious implications for the speed at which the loop is executed.
To give some more technical detail about this optimization, here's a quote from the ceval.c
file (the "engine" of Python's virtual machine):
Some opcodes tend to come in pairs thus making it possible to predict the second code when the first is run. For example,
GET_ITER
is often followed byFOR_ITER
. AndFOR_ITER
is often followed bySTORE_FAST
orUNPACK_SEQUENCE
.Verifying the prediction costs a single high-speed test of a register variable against a constant. If the pairing was good, then the processor's own internal branch predication has a high likelihood of success, resulting in a nearly zero-overhead transition to the next opcode. A successful prediction saves a trip through the eval-loop including its two unpredictable branches, the
HAS_ARG
test and the switch-case. Combined with the processor's internal branch prediction, a successfulPREDICT
has the effect of making the two opcodes run as if they were a single new opcode with the bodies combined.
We can see in the source code for the FOR_ITER
opcode exactly where the prediction for STORE_FAST
is made:
case FOR_ITER: // the FOR_ITER opcode case
v = TOP();
x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
if (x != NULL) {
PUSH(x); // put x on top of the stack
PREDICT(STORE_FAST); // predict STORE_FAST will follow - success!
PREDICT(UNPACK_SEQUENCE); // this and everything below is skipped
continue;
}
// error-checking and more code for when the iterator ends normally
The PREDICT
function expands to if (*next_instr == op) goto PRED_##op
i.e. we just jump to the start of the predicted opcode. In this case, we jump here:
PREDICTED_WITH_ARG(STORE_FAST);
case STORE_FAST:
v = POP(); // pop x back off the stack
SETLOCAL(oparg, v); // set it as the new local variable
goto fast_next_opcode;
The local variable is now set and the next opcode is up for execution. Python continues through the iterable until it reaches the end, making the successful prediction each time.
The Python wiki page has more information about how CPython's virtual machine works.
참고URL : https://stackoverflow.com/questions/11241523/why-does-python-code-run-faster-in-a-function
'programing tip' 카테고리의 다른 글
Python을 사용하여 HTTP를 통해 파일을 다운로드하려면 어떻게해야합니까? (0) | 2020.09.29 |
---|---|
변수가 함수 유형인지 확인 (0) | 2020.09.29 |
JSF, Servlet 및 JSP의 차이점은 무엇입니까? (0) | 2020.09.29 |
“git merge -s ours”의“그들의”버전이 있습니까? (0) | 2020.09.29 |
Java를 사용하여 한 줄씩 큰 텍스트 파일을 읽는 방법은 무엇입니까? (0) | 2020.09.29 |