programing

함수에서 Python 코드가 더 빨리 실행되는 이유는 무엇입니까?

copysource 2022. 9. 18. 17:41
반응형

함수에서 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테스트 및 스위치 케이스.한 브랜치 예측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                                     

PREDICTif (*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

반응형