ݺߣ

ݺߣShare a Scribd company logo
모두의 JIT 컴파일러
성우경
왓 스튜디오, 넥슨 코리아
발표자
성우경, 게임 개발자
<야생의 땅: 듀랑고> 서버 프로그래머
왓 스튜디오 / 넥슨 코리아
파이썬
파이썬은 인터프리터
소스코드를 직접 실행
빌드 없이, 바로 실행하기 때문에 디버깅이 편함
기계어를 직접 실행하지는 않음
그래서 컴파일된 언어보다 느림
JIT 컴파일
Just-in-time compilation
프로그램을 실제 실행하는 시점에 기계어로 컴파일
인터프리터 속도를 향상 시키는 방법
파이썬은 PyPy라는 JIT 컴파일러가 존재 1
3.62
7.43
CPython
2.7
PyPy
1.5
PyPy
5.7
속도
이번 발표는
PyPy를 다루지 않습니다.
PyPy 보다 좋게 만들 수도 없습니다…
간단한 JIT 컴파일러를 만드는 자리
소스코드는 github에 등록
어려운 거 아닐까?
소스코드를 기계어로 변환
기계어를 실행
다시 실행될 때 캐싱 된 기계어를 다시 실행
이 모든 것을 런타임에…
간단해요
직접 기계어를 변환하는 건 저에게도 너무 어려워요
파이썬엔 강력한 도구가 많고 그걸 사용하겠습니다
레시피
파이썬 코드 파싱 ast
C언어 변환 cython
컴파일 distutils
모듈 동적 로드 importlib
Cython
파이썬 문법을 기반
파이썬 코드를 C 언어로 변환
변환된 소스코드(C 언어)를 컴파일
컴파일된 모듈을 파이썬과 연동
Cython 코드
def integrate_f(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0
dx = (b - a)/N
for i in range(N):
s += f(a + I * dx)
return s * dx
Cython
차이점은 변수 타입을 선언
선언하지 않아도 문제 없습니다
변수 타입을 지정하면 속도 증가
Cython 코드
def integrate_f(double a, double b, int N):
cdef int i
cdef double s, dx
s = 0
dx = (b - a)/N
for i in range(N):
s += f(a + I * dx)
return s * dx
JIT 컴파일 과정
JIT 컴파일 과정
JIT 함수 선언
JIT 함수 실행
Cython 코드 생성
C 코드 생성
컴파일
모듈 로드
실행
JIT 컴파일 과정
JIT 함수 선언
JIT 함수 실행(런타임)
Cython 코드 생성(런타임)
C 코드 생성(런타임)
컴파일(런타임)
모듈 로드(런타임)
실행
JIT 함수 선언
@jit
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
데코레이터로 표시
이 함수가 실행되면 JIT 컴파일
JIT 함수 호출
result = f(1, 500) 호출 시 인자의 타입 정보를 가지고 JIT 컴파일 시작
1은 정수형
500도 정수형
Cython 코드 생성
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
함수 내부의 변수 타입을 찾자(코드를 파싱)
import ast
import inspect
node = ast.parse(inspect.getsource(f))
Cython 코드 생성
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
함수 코드의 AST(Abstract Syntax Tree) 추출
Cython 코드 생성
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
변수 res 는 실수형
Cython 코드 생성
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
변수 i, j 는 정수형
Cython 코드 생성
AST를 다시 코드로 변환하여 Cython 코드 생성 def jit_f(I, J):
res = 0.0
for i in range(I):
for j in range(J * I):
res += 1
return res
Cython 코드 생성
변수 타입 정보를 가지고 Cython 변수 선언
def jit_f(long I, long J):
cdef long j
cdef long i
cdef double res
res = 0.0
for i in range(I):
for j in range(J * I):
res += 1
return res
C 코드 생성
def jit_f(long I, long J):
cdef long j
cdef long i
cdef double res
res = 0.0
for i in range(I):
for j in range(J * I):
res += 1
return res
from Cython.Build.Dependencies import Cythonize
.....
Cythonize(.....)
.....
자동 생성된 C코드
C 코드 생성
컴파일
from distutils.command.build_ext import build_ext
build_extension = build_ext(.....)
.....
build_extension.run()
import imp
Cython_module = imp.load_dynamic(name, path)
f = Cython_module.jit_f
모듈 동적 로드
함수 f는 컴파일 된 함수(jit_f)로 교체
실행
result = f(1, 500)
result = jit_f(1, 500)
def jit(func):
def wrapper(*args, **kwargs):
func 코드를 Cython 코드로 변환
Cython 코드, C 코드 컴파일
모듈 동적 로드
return jit_func(*args, **kwargs)
return wrapper
실행
result = f(1, 500)
result = jit_f(1, 500)
컴파일 된 함수 jit_f(1, 500) 실행
1000000000.0 을 반환하여 result 에 저장
만약 또 f 함수가 실행 된다면?
이미 준비 된 jit_f를 바로 실행
성능
소수 찾기
def primes(kmax):
result = list()
p = [0 for i in range(1000)]
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
result.append(n)
n = n + 1
return result
def primes(kmax):
result = list()
p = [0 for i in range(1000)]
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
result.append(n)
n = n + 1
return result
성능
소수 찾기
2.7 배 증가
성능
예제 코드 def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
성능
예제 코드
36 배 증가
결론
Python 코드를 기계어로 컴파일
컴파일 된 기계어를 로딩하여 실행
같은 코드가 반복 실행되면 캐싱 된 기계어를 직접 실행
위 과정을 런타임에 수행하는 JIT 컴파일러 구현
그러나
간단한 JIT 컴파일러의 제약
함수가 최초로 실행될 때 컴파일 시간이 필요합니다
Python  Cython  C  Binary  Run
자주 반복되지 않으면 직접 실행보다 느릴 수 있습니다
자주 반복되는 코드에 효과적
메모리 누수 발생 가능성?! 예외 처리?!
그래도
프로토타입은 Python 코드  LLVM 중간코드 생성
복잡한 과정 없이 단순하게 LLVM 중간코드 생성
성숙된 LLVM 기술 사용 가능
LLVM 중간코드 생성
@llvm.jit
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
LLVM 중간코드 생성
@llvm.jit
def f(I, J):
res = 0.0
for i in range(I):
for j in range (J * I):
res += 1
return res
마지막으로
LLVM 을 사용하면 효과적인 JIT 컴파일러 제작
LLVM 을 설명하기에는 여백이 부족…
휼륭한 LLVM 기반 JIT 컴파일러가 존재?!
https://numba.pydata.org/
감사니다

More Related Content

모두의 JIT 컴파일러

  • 1. 모두의 JIT 컴파일러 성우경 왓 스튜디오, 넥슨 코리아
  • 2. 발표자 성우경, 게임 개발자 <야생의 땅: 듀랑고> 서버 프로그래머 왓 스튜디오 / 넥슨 코리아
  • 4. 파이썬은 인터프리터 소스코드를 직접 실행 빌드 없이, 바로 실행하기 때문에 디버깅이 편함 기계어를 직접 실행하지는 않음 그래서 컴파일된 언어보다 느림
  • 5. JIT 컴파일 Just-in-time compilation 프로그램을 실제 실행하는 시점에 기계어로 컴파일 인터프리터 속도를 향상 시키는 방법 파이썬은 PyPy라는 JIT 컴파일러가 존재 1 3.62 7.43 CPython 2.7 PyPy 1.5 PyPy 5.7 속도
  • 6. 이번 발표는 PyPy를 다루지 않습니다. PyPy 보다 좋게 만들 수도 없습니다… 간단한 JIT 컴파일러를 만드는 자리 소스코드는 github에 등록
  • 7. 어려운 거 아닐까? 소스코드를 기계어로 변환 기계어를 실행 다시 실행될 때 캐싱 된 기계어를 다시 실행 이 모든 것을 런타임에…
  • 8. 간단해요 직접 기계어를 변환하는 건 저에게도 너무 어려워요 파이썬엔 강력한 도구가 많고 그걸 사용하겠습니다
  • 9. 레시피 파이썬 코드 파싱 ast C언어 변환 cython 컴파일 distutils 모듈 동적 로드 importlib
  • 10. Cython 파이썬 문법을 기반 파이썬 코드를 C 언어로 변환 변환된 소스코드(C 언어)를 컴파일 컴파일된 모듈을 파이썬과 연동 Cython 코드 def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b - a)/N for i in range(N): s += f(a + I * dx) return s * dx
  • 11. Cython 차이점은 변수 타입을 선언 선언하지 않아도 문제 없습니다 변수 타입을 지정하면 속도 증가 Cython 코드 def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b - a)/N for i in range(N): s += f(a + I * dx) return s * dx
  • 13. JIT 컴파일 과정 JIT 함수 선언 JIT 함수 실행 Cython 코드 생성 C 코드 생성 컴파일 모듈 로드 실행
  • 14. JIT 컴파일 과정 JIT 함수 선언 JIT 함수 실행(런타임) Cython 코드 생성(런타임) C 코드 생성(런타임) 컴파일(런타임) 모듈 로드(런타임) 실행
  • 15. JIT 함수 선언 @jit def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 데코레이터로 표시 이 함수가 실행되면 JIT 컴파일
  • 16. JIT 함수 호출 result = f(1, 500) 호출 시 인자의 타입 정보를 가지고 JIT 컴파일 시작 1은 정수형 500도 정수형
  • 17. Cython 코드 생성 def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 함수 내부의 변수 타입을 찾자(코드를 파싱) import ast import inspect node = ast.parse(inspect.getsource(f))
  • 18. Cython 코드 생성 def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 함수 코드의 AST(Abstract Syntax Tree) 추출
  • 19. Cython 코드 생성 def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 변수 res 는 실수형
  • 20. Cython 코드 생성 def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 변수 i, j 는 정수형
  • 21. Cython 코드 생성 AST를 다시 코드로 변환하여 Cython 코드 생성 def jit_f(I, J): res = 0.0 for i in range(I): for j in range(J * I): res += 1 return res
  • 22. Cython 코드 생성 변수 타입 정보를 가지고 Cython 변수 선언 def jit_f(long I, long J): cdef long j cdef long i cdef double res res = 0.0 for i in range(I): for j in range(J * I): res += 1 return res
  • 23. C 코드 생성 def jit_f(long I, long J): cdef long j cdef long i cdef double res res = 0.0 for i in range(I): for j in range(J * I): res += 1 return res from Cython.Build.Dependencies import Cythonize ..... Cythonize(.....) .....
  • 24. 자동 생성된 C코드 C 코드 생성
  • 25. 컴파일 from distutils.command.build_ext import build_ext build_extension = build_ext(.....) ..... build_extension.run()
  • 26. import imp Cython_module = imp.load_dynamic(name, path) f = Cython_module.jit_f 모듈 동적 로드 함수 f는 컴파일 된 함수(jit_f)로 교체
  • 27. 실행 result = f(1, 500) result = jit_f(1, 500) def jit(func): def wrapper(*args, **kwargs): func 코드를 Cython 코드로 변환 Cython 코드, C 코드 컴파일 모듈 동적 로드 return jit_func(*args, **kwargs) return wrapper
  • 28. 실행 result = f(1, 500) result = jit_f(1, 500) 컴파일 된 함수 jit_f(1, 500) 실행 1000000000.0 을 반환하여 result 에 저장 만약 또 f 함수가 실행 된다면? 이미 준비 된 jit_f를 바로 실행
  • 29. 성능 소수 찾기 def primes(kmax): result = list() p = [0 for i in range(1000)] k = 0 n = 2 while k < kmax: i = 0 while i < k and n % p[i] != 0: i = i + 1 if i == k: p[k] = n k = k + 1 result.append(n) n = n + 1 return result
  • 30. def primes(kmax): result = list() p = [0 for i in range(1000)] k = 0 n = 2 while k < kmax: i = 0 while i < k and n % p[i] != 0: i = i + 1 if i == k: p[k] = n k = k + 1 result.append(n) n = n + 1 return result 성능 소수 찾기 2.7 배 증가
  • 31. 성능 예제 코드 def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res
  • 32. def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res 성능 예제 코드 36 배 증가
  • 33. 결론 Python 코드를 기계어로 컴파일 컴파일 된 기계어를 로딩하여 실행 같은 코드가 반복 실행되면 캐싱 된 기계어를 직접 실행 위 과정을 런타임에 수행하는 JIT 컴파일러 구현
  • 34. 그러나 간단한 JIT 컴파일러의 제약 함수가 최초로 실행될 때 컴파일 시간이 필요합니다 Python  Cython  C  Binary  Run 자주 반복되지 않으면 직접 실행보다 느릴 수 있습니다 자주 반복되는 코드에 효과적 메모리 누수 발생 가능성?! 예외 처리?!
  • 35. 그래도 프로토타입은 Python 코드  LLVM 중간코드 생성 복잡한 과정 없이 단순하게 LLVM 중간코드 생성 성숙된 LLVM 기술 사용 가능
  • 36. LLVM 중간코드 생성 @llvm.jit def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res
  • 37. LLVM 중간코드 생성 @llvm.jit def f(I, J): res = 0.0 for i in range(I): for j in range (J * I): res += 1 return res
  • 38. 마지막으로 LLVM 을 사용하면 효과적인 JIT 컴파일러 제작 LLVM 을 설명하기에는 여백이 부족… 휼륭한 LLVM 기반 JIT 컴파일러가 존재?! https://numba.pydata.org/