함수에서 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
왜 이러한가?
함수 내 바이트 코드는 다음과 같습니다.
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
다른 점은 이 보다 고속(!)이라는 점입니다.함수에서i
로컬이지만 최상위 레벨에서는 글로벌합니다.
바이트 코드를 조사하려면 모듈을 사용합니다.함수를 직접 분해할 수 있었지만, 최상위 코드를 분해하려면 빌트인을 사용해야 했습니다.
글로벌 변수보다 로컬 변수를 저장하는 것이 더 빠른 이유를 물을 수 있습니다.이것은 CPython 구현 세부 사항입니다.
CPython은 인터프리터가 실행하는 바이트 코드로 컴파일됩니다.함수를 컴파일할 때 로컬 변수는 고정 크기 배열에 저장됩니다.dict
및 및 변수 이름은 인덱스에 할당됩니다.이는 함수에 로컬 변수를 동적으로 추가할 수 없기 때문에 가능합니다.은 문자 입니다.PyObject
츠미야
을 글로벌과 대조해 (「 」LOAD_GLOBAL
dict
해시 등을 포함한 검색입니다. 그래서 '', '아까', '아까', '아까', '아까'를 지정해야 합니다.global i
, 는 「」를 합니다.STORE_FAST
액세스하지 않도록 지시하지 않는 한 액세스 할 수 있습니다.
덧붙여서, 글로벌 검색은 여전히 매우 최적화되어 있습니다.Atribute ( 룩업"foo.bar
정말 느린 사람들이야!
다음은 로컬 가변 효율에 대한 작은 그림입니다.
로컬/글로벌 변수 저장 시간을 제외하고 opcode 예측은 함수를 더 빠르게 만듭니다.
한 것처럼 이 는 '먹다', '먹다', '먹다'를 합니다.STORE_FAST
opcode(opcode)하다
>> 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
보통 프로그램이 실행되면 Python은 스택을 추적하고 각 opcode가 실행된 후 스택 프레임에 대한 다른 체크를 미리 준비하면서 각 opcode를 차례로 실행합니다.Opcode 예측은 특정 경우에 Python이 다음 opcode로 직접 이동할 수 있다는 것을 의미하며, 따라서 이러한 오버헤드를 일부 피할 수 있습니다.
이 경우 Python이 볼 때마다FOR_ITER
(루프 상단의) 이 "고정"됩니다.STORE_FAST
실행되어야 하는 다음 opcode입니다.Python은 다음 opcode를 훔쳐보고 예측이 맞다면 바로 로 이동합니다.STORE_FAST
이것은 두 개의 opcode를 하나의 opcode로 압축하는 효과가 있습니다.
한편,STORE_NAME
opcode는 글로벌레벨의 루프에서 사용됩니다.Python은 이 opcode를 볼 때 *not* 유사한 예측을 합니다.대신 루프가 실행되는 속도에 분명한 영향을 미치는 평가 루프의 선두로 돌아가야 합니다.
이 최적화에 대한 기술적인 세부 정보를 제공하기 위해 파일(Python 가상 머신의 "엔진")에서 인용한 내용을 소개합니다.
일부 opcode는 쌍을 이루는 경향이 있기 때문에 첫 번째 코드가 실행될 때 두 번째 코드를 예측할 수 있습니다.예를들면,
GET_ITER
가 뒤따르는 경우가 많다.FOR_ITER
. 그리고 종종 또는 그 뒤에 이어집니다.UNPACK_SEQUENCE
.예측 검증에는 상수에 대한 레지스터 변수의 단일 고속 테스트가 필요합니다.페어링이 양호하면 프로세서의 내부 브랜치 예측이 성공할 가능성이 높기 때문에 다음 opcode로 오버헤드가 거의 발생하지 않습니다.예측이 성공하면 2개의 예측 불가능한 브랜치, 즉 valval-loop을 통한 트립이 절약됩니다.
HAS_ARG
테스트 및 스위치 케이스.한 브랜치 예측PREDICT
opcode와 조합으로 2개의 opcode를 1개의 할 수 있습니다.
opcode의 소스코드에서 예측이 정확히 어디에 있는지 알 수 있습니다.STORE_FAST
★★★★
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
PREDICT
이 if (*next_instr == op) goto PRED_##op
즉, 예측된 운영 코드의 선두로 이동합니다.때는 이렇게합니다.
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;
이것으로 로컬 변수가 설정되고 다음 opcode가 실행 중입니다.Python은 끝에 도달할 때까지 반복할 수 있는 상태를 지속하며 매번 성공적인 예측을 합니다.
Python wiki 페이지에는 CPython의 가상 시스템이 작동하는 방식에 대한 자세한 정보가 나와 있습니다.
언급URL : https://stackoverflow.com/questions/11241523/why-does-python-code-run-faster-in-a-function
'programing' 카테고리의 다른 글
JSF 백업 빈 구조(베스트 프랙티스) (0) | 2022.09.18 |
---|---|
Maria DB 스토리지 용량 및 가격 (0) | 2022.09.18 |
Javascript는 일치하는 그룹에 대한 참조로 대체합니까? (0) | 2022.09.18 |
PHP에서 클라이언트 IP 주소를 가져오는 방법 (0) | 2022.09.18 |
장고 시험 앱 오류-:Demand을 담은 데이터베이스를 구축을 부인했다고 오류가 테스트 데이터베이스를 만들었는데요. (0) | 2022.09.18 |