이전에 작성한 글
https://jspark33.tistory.com/114
에서 인터프리터에 대하여 알아보며
인터프리터 언어 중에 하나인 파이썬의 인터프리터는 어떻게 구성되어있을지 매우 궁금하였다.
파이썬 인터프리터에 대해 알아보려면
파이썬의 기본 구현체인 cPython을 살펴보아야 한다.
https://github.com/python/cpython
CPyton의 소스 코드는 여러 디렉터리로 구성되어 있다.
그중에서도 Python/ 안에 위치해있는
ceval.c가 파이썬 바이트코드를 실행하는 메인 루프로서, 인터프리터의 핵심이라 할 수 있다.
https://github.com/python/cpython/blob/main/Python/ceval.c
이 ceval.c 파일 내에 있는 PyEval_EvalFrameEx()함수는 바이트코드를 해석하고 실행하는 메인 루프이다.
이 함수는 바이트코드를 가져와 switch문을 사용해 각각의 명령을 처리한다.
PyEval_EvalFrameEx()함수에 대해 파고 들어보자면
무한 루프 형태로 구현된 함수로서
한 명령어씩 바이트코드를 읽고 실행한다.
바이트 코드의 명령어(opcode)를 가져오고, 이에 따라 실행할 동작을 결정하게된다.
이 때 각 명령어는 switch-case문으로 처리된다
switch (opcode) {
case LOAD_FAST:
// 로컬 변수를 스택에 로드
break;
case BINARY_ADD:
// 스택에서 두 값을 더함
break;
...
}
모든 바이트코드가 끝까지 실행되면 PyEval_EvalFrameEx는 루프를 종료한다.
잠깐 파이썬 코드와 그에 따른 바이트코드에 따른 실행과정을 한 번 살펴보자.
파이썬 코드
x = 1
y = 2
z = x + y
바이트 코드
1 0 LOAD_CONST 1 (1)
2 STORE_FAST 0 (x)
2 4 LOAD_CONST 2 (2)
6 STORE_FAST 1 (y)
3 8 LOAD_FAST 0 (x)
10 LOAD_FAST 1 (y)
12 BINARY_ADD
14 STORE_FAST 2 (z)
16 RETURN_VALUE
PyEval_EvalFrameEx 처리 흐름:
- LOAD_CONST 1: 상수 1을 스택에 푸시.
- STORE_FAST x: 스택에서 값을 팝하고 로컬 변수 x에 저장.
- LOAD_CONST 2: 상수 2를 스택에 푸시.
- STORE_FAST y: 스택에서 값을 팝하고 로컬 변수 y에 저장.
- LOAD_FAST x: 로컬 변수 x의 값을 스택에 푸시.
- LOAD_FAST y: 로컬 변수 y의 값을 스택에 푸시.
- BINARY_ADD: 스택의 두 값을 더하고 결과를 푸시.
- STORE_FAST z: 스택의 결과를 로컬 변수 z에 저장.
- RETURN_VALUE: 결과값을 반환하고 종료.
위와 같은 형식으로 파이썬 코드 -> 바이트 코드 -> 인터프리터가 실행되며 코드가 동작하는 것이다.
전에 작성한 게시글에서 말했듯
인터프리터는 결국 big while swtich문이라 할 수 있다.
예시로 wamr 을 직접 빌드하고
디버거로 코드의 실행과정을 따라가보면
switch 문 안에서 런타임이 계속 동작하는 것을 눈으로 확인할 수 있다.
과연 그렇다면 CPython에서도 wamr처험 while switch 코드를 눈으로 확인할 수 있을까?
답은 NO! 다.
왜냐하면 파이썬 인터프리터는 디스패치 테이블이라는 방식을 사용하여 switch 문을 구성하였기에
다른 런타임들과 달리 switch문이 눈에 보이지 않는다.
디스패치 테이블이란
static void* dispatch_table[] = {&&do_halt, &&do_inc, &&do_dec, ...};
#define DISPATCH()
goto *dispatch_table[code[pc++]]
간단하게 말하자면 위 코드와 같이 goto를 활용하여 해당 opcode의 코드로 직접 점프하는 방식이다.
일반적인 switch case문에 비해 15-25% 정도 성능이 향상되어
VM이나 인터프리터에서 자주 사용된다고 한다.
과정을 분석해보니
파이썬 코드가 바이트코드로 변환되고
변환된 바이트코드 명령어(op code)rk
파이썬 인터프리터 코드인 ceval.c에서
PyEval_EvalFrameEx에서는 _PyEvalFrameDefault를 호출하고
_PyEvalFrameDefault안에서 USE_COMPUTED_GOTO가 실행된다.
이 USE_COMPUTED_GOTO는
ceval_macros.h에서
DISPATCH_GOTO()를 통해 디스패치 테이블 방식으로 실행되고
파이썬 바이트 코드의 명령어가 작동되는 코드로 goto한다.
generated_cases.c에서
해당 OPcode로 향해 바이트 코드 명령어가 실행되게 된다.
https://github.com/python/cpython/blob/main/Python/generated_cases.c.h
요약하자면
파이썬 인터프리터는 디스패치 테이블 방식을 이용하여 switch case문을 구성하여
더 효율적인 인터프리터를 구현했다!
'Python' 카테고리의 다른 글
터미널에서 한번에 여러개의 파일 만들기 (0) | 2021.06.29 |
---|---|
Python csv를 list로 변환 (0) | 2020.09.12 |
파이썬으로 문자를 보내보자-twilio (0) | 2020.04.21 |