함수에서 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_GLOBALdict해시 등을 포함한 검색입니다. 그래서 '', '아까', '아까', '아까', '아까'를 지정해야 합니다.global i, 는 「」를 합니다.STORE_FAST액세스하지 않도록 지시하지 않는 한 액세스 할 수 있습니다.
덧붙여서, 글로벌 검색은 여전히 매우 최적화되어 있습니다.Atribute ( 룩업"foo.bar정말 느린 사람들이야!
다음은 로컬 가변 효율에 대한 작은 그림입니다.
로컬/글로벌 변수 저장 시간을 제외하고 opcode 예측은 함수를 더 빠르게 만듭니다.
한 것처럼 이 는 '먹다', '먹다', '먹다'를 합니다.STORE_FASTopcode(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_NAMEopcode는 글로벌레벨의 루프에서 사용됩니다.Python은 이 opcode를 볼 때 *not* 유사한 예측을 합니다.대신 루프가 실행되는 속도에 분명한 영향을 미치는 평가 루프의 선두로 돌아가야 합니다.
이 최적화에 대한 기술적인 세부 정보를 제공하기 위해 파일(Python 가상 머신의 "엔진")에서 인용한 내용을 소개합니다.
일부 opcode는 쌍을 이루는 경향이 있기 때문에 첫 번째 코드가 실행될 때 두 번째 코드를 예측할 수 있습니다.예를들면,
GET_ITER가 뒤따르는 경우가 많다.FOR_ITER. 그리고 종종 또는 그 뒤에 이어집니다.UNPACK_SEQUENCE.예측 검증에는 상수에 대한 레지스터 변수의 단일 고속 테스트가 필요합니다.페어링이 양호하면 프로세서의 내부 브랜치 예측이 성공할 가능성이 높기 때문에 다음 opcode로 오버헤드가 거의 발생하지 않습니다.예측이 성공하면 2개의 예측 불가능한 브랜치, 즉 valval-loop을 통한 트립이 절약됩니다.
HAS_ARG테스트 및 스위치 케이스.한 브랜치 예측PREDICTopcode와 조합으로 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 |