티스토리 뷰
안녕하세요. Teus입니다.
이번 포스팅은, what's new in python3.13 에서 새롭게 소개된 실험적 기능 중 하나인
python jit compiler를 빌드하는 방법을 소개하고, 간단하게 일반 python과의 성능 차이에 대해서 알아봅니다.
0. JIT
PEP 744 – JIT Compilation
GIL때문에 관심을 전혀 받지 못하고 있었지만,
Python자체적으로 JIT(Just In Time) Compiler가 준비되고 있었고
해당 기능이 이제 Experimental Feature로 사용이 가능하게 되었습니다.
Python의 Jit같은 경우, “copy-and-patch” 기술을 사용해서 High-level로 작성된 코드의 byte code 최적화를 가능하게 해줍니다.
혹시 copy-and-patch 기술이 궁금하실 경우, 아래 내용들을 참고하시면 됩니다.
https://dl.acm.org/doi/10.1145/3485513
https://www.youtube.com/watch?v=HxSHIpEQRjs&feature=youtu.be
https://sillycross.github.io/2023/05/12/2023-05-12/
1. jit Compiler 빌드하기
먼저 python 3.13.0 버전 소스파일일을 curl을 통해서 받아옵니다.
export PYTHON_VERSION=3.13.0
export PYTHON_MAJOR=3
curl -O https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz
이때 PYTHON JIT같은경우, llvm컴파일러를 요구합니다.
컴파일러 환경 및 llvm 18, 19 를 설치해 줍니다
sudo apt-get install build-essential gcc zlib1g-dev
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo ./llvm.sh 19
https://github.com/python/cpython/blob/main/Tools/jit/README.md
설치 official Readme에서는 단순하게 llvm 19를 설치하라고 안내하고 있습니다.
하지만 실제로 llvm 19만 설치한 다음 나머지 과정을 진행할 경우
clang-18이 존재하지 않는다는 에러를 보여주면서 설치를 실패하게 됩니다.
그리고, 받아온 Python파일을 압축 해제한 뒤 소스코드로부터 빌드를 진행합니다.
tar -xvzf Python-${PYTHON_VERSION}.tgz
cd Python-${PYTHON_VERSION}
./configure --enable-experimental-jit --with-zlib
sudo make
sudo make install
wsl2 ubuntu의 문제인지는 모르겠지만, --with-zlib가 없을경우, make install 과정에서 에러가 발생합니다.
이렇게 실행한 다음
아래 코드를 실행해 보면, 설치된 Python의 JIT을 사용하는지, 사용하지 않는지 확인할 수 있습니다.
import _testinternalcapi
print(f"JIT is enabled:{_testinternalcapi.get_optimizer()}")
이때 PYTHON_JIT이라는 환경변수의 값을 변경 함 으로써 JIT사용여부를 활성화 할 수 있습니다.
초기 빌드 과정에서 설정을 통해서 기본적으로 사용할 지 말지 여부를 결정할 수가 있습니다
2. JIT을 사용한 벤치마크
벤치마크에 앞서, JIT은 실행 전과, 실행 후 Just In Time Compile이 되면서 byte코드가 변하게 됩니다.
import dis
def list_add(a, b):
temp = []
for idx in range(len(a)):
temp.append(a[idx]+b[idx])
return temp
#해당 함수가 사용한 Byte Code를 출력해줌
for instruction in dis.Bytecode(list_add, adaptive=True):
print(instruction.opname, instruction.argrepr)
lt1 = [i for i in range(500)]
lt2 = [i for i in range(500)]
list_add(lt1, lt2)
list_add(lt1, lt2)
print("="*100)
for instruction in dis.Bytecode(list_add, adaptive=True):
print(instruction.opname, instruction.argrepr)
설명 | 첫번째 실행 전 | 두번 실행 후 |
---|---|---|
코드 실행 재개 | RESUME | RESUME_CHECK |
새로운 빈 리스트 생성 | BUILD_LIST | BUILD_LIST |
temp 변수에 리스트 저장 |
STORE_FAST temp | STORE_FAST temp |
전역 변수 range 로드 |
LOAD_GLOBAL range | LOAD_GLOBAL_BUILTIN range |
전역 변수 len 로드 |
LOAD_GLOBAL len | LOAD_GLOBAL_BUILTIN len |
로컬 변수 a 로드 |
LOAD_FAST a | LOAD_FAST a |
함수 호출 | CALL | CALL_LEN |
함수 호출 | CALL | CALL_BUILTIN_CLASS |
반복 가능한 객체로 변환 (이터레이터 생성) | GET_ITER | GET_ITER |
반복문 시작, L2 로 점프 |
FOR_ITER to L2 | FOR_ITER_RANGE to L2 |
idx 변수에 현재 반복값 저장 |
STORE_FAST idx | STORE_FAST idx |
temp 변수 로드 |
LOAD_FAST temp | LOAD_FAST temp |
temp 의 append 메소드 호출 |
LOAD_ATTR append | LOAD_ATTR_METHOD_NO_DICT append |
a[idx] 값 로드 |
LOAD_FAST a, idx | LOAD_FAST a, idx |
인덱싱 연산 (a[idx] ) |
BINARY_SUBSCR | BINARY_SUBSCR_LIST_INT |
b[idx] 값 로드 |
LOAD_FAST b, idx | LOAD_FAST b, idx |
인덱싱 연산 (b[idx] ) |
BINARY_SUBSCR | BINARY_SUBSCR_LIST_INT |
덧셈 연산 | BINARY_OP + | BINARY_OP_ADD_INT |
함수 호출 | CALL | CALL_LIST_APPEND |
스택에서 가장 위의 값 제거 | POP_TOP | POP_TOP |
반복문 첫 번째 부분으로 점프 (L1 ) |
JUMP_BACKWARD to L1 | JUMP_BACKWARD to L1 |
반복문 종료 | END_FOR | END_FOR |
스택에서 가장 위의 값 제거 | POP_TOP | POP_TOP |
temp 변수 로드 |
LOAD_FAST temp | LOAD_FAST temp |
temp 반환값 반환 |
RETURN_VALUE | RETURN_VALUE |
위와같이 ByteCode Level에서 최적화가 진행되고 있는 것을 볼 수 있습니다.
그러면, 실제 벤치마크를 진행 해보겠습니다.
import dis
def list_add(a, b):
temp = []
for idx in range(len(a)):
temp.append(a[idx]+b[idx])
return temp
if __name__ == "__main__":
import time
lt1 = lambda x : [i for i in range(x*500000)]
lt2 = lambda x : [i for i in range(x*500000)]
fin_ret = []
for i in range(20):
st = time.perf_counter()
list_add(lt1(i), lt2(i))
fin_ret.append(time.perf_counter()-st)
print(fin_ret)
결과가 참 아이러니하게도, 오히려 jit을 사용했을 때 느려지는 것을 확인할 수 있습니다.
이는 reference로 사용한 문서에서도 역시 JIT을 사용했을 때 효과가 거의 미비한 것을 보여주고 있습니다.
아무래도 아직까지 실험 기능인 만큼, 충분히 개선 된 이후에 성능의 향상을 기대해 볼 수 있지 않을까 예상됩니다.
Reference
'Python 잡지식' 카테고리의 다른 글
[Python]Free-threaded CPython(3.13버전 릴리즈) (0) | 2024.11.13 |
---|---|
[Python]PEP703. CPython의 GIL을 선택사항으로 관련해서 (0) | 2024.11.13 |
[Python]MultiProcessing 탐구 (0) | 2024.08.21 |
[Python]Python의 asyncio 탐구 (0) | 2024.07.10 |
[Python]비동기로 parquet 파일 읽어들이기 (0) | 2024.07.06 |
- Total
- Today
- Yesterday
- SIMD
- C++
- 알고리즘
- 컴퓨터그래픽스
- 프로그래머스
- 동적계획법
- 완전탐색 알고리즘
- 코딩테스트
- 사칙연산
- hash
- stack
- 이분탐색
- Python
- Search알고리즘
- 병렬처리
- heap
- prime number
- Sort알고리즘
- 분할정복
- Greedy알고리즘
- GDC
- 자료구조
- AVX
- git
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |