24. PyPy
• PyPy: (흔히) 파이썬으로 만들어진 파이썬 구현체
• CPython보다 빠르다던데?
• 파이썬으로 구현했는데 어떻게?
인터프리터를 만들 도구
25. PyPy
• PyPy: (흔히) 파이썬으로 만들어진 파이썬 구현체
• 인터프리터 작성을 위한 언어인 RPython
• RPython으로 작성된 파이썬 인터프리터
• PyPy는 두 가지를 *모두* 포함하는 프로젝트입니다
인터프리터를 만들 도구
26. PyPy
• PyPy: (흔히) 파이썬으로 만들어진 파이썬 구현체
• 인터프리터 작성을 위한 언어인 RPython
• RPython으로 작성된 파이썬 인터프리터
• PyPy는 두 가지를 *모두* 포함하는 프로젝트입니다
인터프리터를 만들 도구
27. RPython
• RPython: 정적으로 컴파일 되지만, 제약이 있는 파이썬
• 정적으로 컴파일 할 수 있는 코드만 지원합니다
• python2 의 부분집합(과 비슷)입니다
• 보너스! JIT 컴파일러 툴체인이 내장되어 있어요!
• 편리하고 강력한 언어로 빠르게 인터프리터를 만들어(RPython)
빠르게 실행(JIT)합니다. => PyPy
Restricted Python
28. JIT 컴파일러
• JIT 컴파일?
• 자주 사용되는 코드를 런타임에 추적해서,
• 최적화 후 컴파일 하여 실행시간에 바이너리를 생성합니다
• 런타임에만 알 수 있는 정보로 적극적인 최적화도 할 수 있어요
29. RPython
• RPython: 정적으로 컴파일 되지만, 제약이 있는 파이썬
• 제약이 있지만, C와 같은 언어보다 편리합니다.
• BigInt, Unicode 처리 등....
• RPython의 해당 기능을 이용하면 구현이 공짜!
Restricted Python
30. RPython
• RPython: 정적으로 컴파일 되지만, 제약이 있는 파이썬
• 게다가 JIT 컴파일러 툴체인이 내장되어 있다는데...
• CPython보다 PyPy가 더 빠른 비결
• 엄청나게 어려운 JIT 컴파일러 붙이기가 (거의) 공짜!
Restricted Python
32. PyPy, 얼마나 빠른가요?
• PyPy: 7.0x of CPython (2015)
• HippyVM: 7.3x of PHP / 2x of HHVM (2014)
33. PyPy, 얼마나 빠른가요?
• PyPy: 7.0x of CPython (2015)
• HippyVM: 7.3x of PHP / 2x of HHVM (2014)
• HHVM에 비해 적은 인원, 적은 코드로 더 좋은 성능
34. PyPy, 왜 빠른가요?
• RPython으로 인터프리터를 작성하고,
• 제공되는 툴체인으로 JIT를 하기 위한 힌트를 추가하면,
• 인터프리터가 실행추적 JIT로 변신!
35. PyPy, 왜 빠른가요?
• 실행추적(tracing) JIT
• 실용적으로 두 번째로 널리 쓰이는 JIT 기법입니다
• 실행추적 JIT는 실행시간에 추적한 루프 단위로 최적화합니다
• 전통적인 JIT
• 자주 함수되는 함수를 프로파일 해서 함수 단위로 컴파일합니다
36. 메타 실행추적 JIT
• RPython을 이용해 실행추적 JIT를 구현한다. (X)
• 인터프리터를 구현하면 실행추적 JIT의 효과를 낸다. (O)
• 어떤 인터프리터라도 (잘 손질하면) 변신!
• “메타”
37. – Arthur C. Clarke,
“Any sufficiently advanced technology is
indistinguishable from magic.”
38. 메타 실행추적 JIT
코드 읽기
실행
결과 저장
인터프리터
…
다음 코드로
JIT
루프 추적
최적화
컴파일
최적화 대상
직접 작성하는 부분JIT 생성기
39. 메타 실행추적 JIT
코드 읽기
실행
결과 저장
인터프리터
…
다음 코드로
JIT
루프 추적
최적화
컴파일
실행 코드
블랙박스
인터프리터 코드가 실행코드를 포함한 정보에 따라
최적화되어서 실행 코드를 최적화하는 효과 발생
40. PyPy, 왜 빠른가요?
• RPython: 메타 실행추적 JIT를 구현한 JIT 툴체인을 포함
• RPython으로 인터프리터를 구현하면
구현된 인터프리터에 실행추적 JIT 컴파일러를 적용합니다
• RPython으로 만들어진 파이썬(PyPy)은
컴파일된 인터프리터가 실행추적 JIT로 실행이 되고
따라서 이 인터프리터로 코드를 실행하면
인터프리터를 JIT하는 과정에서 코드 정보를 포함해서 최적화!
• HippyVM이 작은 코드로 HHVM보다 빨랐던 비결
42. 알파희
• 아희에 적용해 보자!
• (실행추적 JIT를 위해) 루프 추적을 도와주는 것이 핵심
• RPython으로 구현도 하고, RPython으로 구현하기 쉽게 다른
일도 조금 하고...
43. 알파희 1 - 전처리
• 2차원 언어는 런타임이 복잡해지므로 코드 분석 후 선형화
• 본심: RPython을 잘 모르니 가능하면 런타임을 작게 유지하자
• 밯망희
L0: PUSHCHAR ; 밯
BRPOP1 L4 ; ‘망’을 실행할 스택이
충분하지 않으면 L4
로 점프
POPNUM ; 망
HALT ; 희
L4: JMP L0 ;
44. 알파희 2 - 인터프리터
코드 읽기
실행
결과 저장
인터프리터
…
다음 코드로
JIT
루프 추적
최적화
컴파일
실행 코드
블랙박스
45. 알파희 2 - 인터프리터
• 직접 코드를 짜 넣을 부분
• 쉬워요! - 전처리 단계에서 귀찮은걸 많이 해결했으니까~
46. 알파희 2 - 인터프리터
# program: 직렬화된 아희 바이트코드의 리스트
def mainloop(program, debug):
s = Storage()
pc = 0
while pc < len(program): # 코드를 한 줄씩 실행
op, value = program[pc]
if op == OP_ADD:
r1, r2 = s.selected.pop(), s.selected.pop()
s.selected.push(r2 + r1)
elif op == OP_SUB:
…
47. 알파희 2 - 인터프리터
• ※ RPython으로 구현하기는 했지만 아직 JIT를 적용하기 전
$ rpython aheui.py # 컴파일 후 aheui-c 를 생성한다
$ time aheui-c logo.aheui => 23.5s
$ time caheui logo.aheui => 21.5s
• caheui보다 10% 정도 느린 정도로, 상당히 빠른 편
• “시작이 반이다” => 단순히 구현했을 뿐인데 이미 어느정도 빠르다!
• RPython은 파이썬 코드이지만, 네이티브 바이너리로 컴파일 가능하기 때문
48. 알파희 3 - JitDriver
코드 읽기
실행
결과 저장
인터프리터
…
다음 코드로
JIT
루프 추적
최적화
컴파일
실행 코드
블랙박스
50. 알파희 3 - JitDriver
from rpython.rlib.jit import JitDriver
driver = JitDriver(greens=['pc', 'program'], reds=['storage'])
def mainloop():
# initialization codes
pc = 0; storage = Stoarge()
while <cond>:
driver.jit_merge_point(
pc=pc, program=program, storage=storage)
<actual codes>
코드의 실행조건을 결정하는 상수 실행 결과 변경되는 산출물
루프 맨 앞에 삽입하는 것만으로 JIT 동작 시작!
51. 알파희 4 - 루프 추적
코드 읽기
실행
결과 저장
인터프리터
…
다음 코드로
JIT
루프 추적
최적화
컴파일
실행 코드
블랙박스
52. 알파희 4 - 루프 추적
• 루프 추적에 성공해야 최적화가 가능하다
1. green이 모두 같으면 같은 자리로 돌아온 것(루프)으로 판단
2. 같은 경로가 반복되면 최적화 시도
3. 가정이 맞는 동안 계속 최적화 된 코드를 실행
=> 가정이 깨진다면? 최적화에 실패하거나 최적화 된 코드를 버림
53. 알파희 4 - 루프 추적
• jit_merge_point: 핫루프 판단의 기준이 되는 기준점
• 규칙: jit_merge_point 앞에 나타나는 모든 변수는 green 또는
red로 표시해야 합니다
• 동작: green이 모두 일치하면 같은 문맥으로 간주합니다.
같은 문맥이 기준 이상 반복되면 핫루프로 간주합니다.
54. 알파희 4 - 루프 추적
• 이득: 최적화된 코드를 실행하는 만큼 실행 시간 이득을 얻습니다.
• 손실: jit_merge_point에서 코드를 추적하고 최적화를 시도하는
비용이 발생합니다. 최적화 이후에는 최적화된 코드가 올바른 코
드인지 점검하는 실행비용이 발생합니다.
55. 알파희 4 - 루프 추적
프로그램 (코드)
L0: PUSHCHAR
BRPOP1 L4
POPNUM
HALT
L4: JMP L0
프로그램 카운터
현재 코드: L0
메모리 (스택)
_: []
ㄱ: []
···
ㅎ: []
불변
변하지만
같은 값이면
같은 코드 실행
변하고
다른 값이어도
같은 코드 실행
green green red
56. 알파희 4 - 루프 추적
코드
L0: PUSHCHAR
BRPOP1 L4
POPNUM
HALT
L4: JMP L0
프로그램 카운터
현재 코드: L0
메모리 (스택)
_: []
ㄱ: []
···
ㅎ: []
불변
변하지만
같은 값이면
같은 코드 실행
변하고
다른 값이어도
같은 코드 실행
green green red
정말?
57. 알파희 4 - 루프 추적
x = 0
while True:
x += 1
if x < 1000000:
print ‘a’
else:
print ‘b’
<- 메모리에 해당하는 x가
코드 흐름에 영향
58. 알파희 4 - 루프 추적
• 저장공간은 정말 실행에 영향을 끼치지 않을까?
• 저장공간이 스택인지, 큐인지
• 저장공간에서 뽑아낼 값이 충분한지
• 저장공간의 변수가 비교문을 참으로 만드는지
• … 와 같은 이슈가 있지만, “아희”에 대해 파고 들지 말고 통과!
59. 알파희 5 - 루프 추적 최적화
• 기본 동작: 매 명령을 실행할 때마다 jit_merge_point에서 루프
를 찾으려 시도한다
• 수정: “루프가 될 수 있는 지점”을 힌트로 줘서 추적 비용을 줄인
다 (can_enter_jit)
• “루프가 될 수 있는 지점” = jump가 발생하는 지점
• ~= PC가 1씩 늘어나면 절대로 루프가 생기지 않으니까
60. 알파희 5 - 루프 추적 최적화
def mainloop(program, debug):
pc = 0; storage = init_storage()
while pc < program.size:
driver.jit_merge_point(pc=pc, proram=program)
op, operand = program[pc]
if needs_jump(op, operand, storage):
driver.can_enter_jit(pc=pc, program=program)
pc = operand
else:
# other operations
pc += 1
61. 알파희 5 - 루프 추적 최적화
밦밦따빠뚜
뿌뚜뻐뚜뻐
따ㅇㅇㅇ우
ㅇㅇ아ㅇ분
ㅇㅇ초뻐터
ㅇㅇ망희
……
L1: DUP ; L1 뻐
BRZ L6 ; L2 초
PUSH 2 ; L3 아 / 분
SUB ; L4 터
JMP L1 ; L5
L6: POPNUM ; L6 망
HALT ; L7 희
테스트 코드 선형화 코드
5개의 명령어를 반복한다
63. (기타 등등을 생략한) 결과
$ time AHEUI=../rpaheui/aheui-c ./test.sh logo/
real 0m1.795s
user 0m1.087s
sys 0m1.262s
$ time AHEUI=../caheui/aheui ./test.sh logo/
real 0m23.953s
user 0m23.843s
sys 0m0.058s
PyPy/RPython: 짧고 쉬운 코드로 빠른 인터프리터 완성!
64. 생략된 내용
• 작업 로그: https://github.com/aheui/rpaheui/blob/
master/LOG.md
65. 핵심
• PyPy 툴킷이 자동으로 붙여주는 JIT 컴파일러가 핫 루프를 찾아
내 가속합니다.
• 정확하게 찾도록 도와줍시다.
• 불필요하게 깐깐하면: 추적 비용이 증가
• 허술해서 guard가 틀리면: 최적화 비용 낭비
• 쉽게 찾도록 도와줍시다.