Skip to main content
Profiling
실시간 대화식 프로파일러
이흥섭 @ 넥슨 • 왓 스튜디오
PyCon KR 2015
이흥섭
sub@nexon.co.kr • http://subl.ee/ • sublee
• 한글라이즈
http://hangulize.org/
• TrueSkill 파이썬 구현
http://trueskill.org/
• Profiling
• <카트라이더 대시>
• <야생의 땅: 듀랑고>
http://durango.nexon.com/
Profiling
what-studio/profiling
게임서버 최적화
파이썬
프로파일러
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/interactive.gif
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/continuous.gif
게임서버
Linux
CPython 2.7
gevent
요청/응답
시야 내 동기화
생태계 AI
요청/응답 시야 내 동기화 생태계 AI
I/O 바운드 CPU 바운드
CPU 병목이 어딜까?
프로파일러
• 성능 분석 도구
• 함수 별 실행 시간, 호출 횟수, 메모리 사용량 등 수집
• 프로그램 최적화를 보조함.
1. profile/cProfile
2. Yappi
이벤트 기반 프로파일링
• 함수 호출/반환 이벤트 추적
• 실행 시간, 호출 횟수를 조사
• Exclusive ― 서브루틴 제외
• Inclusive ― 서브루틴 포함
def spin(sec):
t = time.time()
while time.time() - t < sec:
pass
def spin5():
spin(5)
spin5()
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/spin_example.py
InclusiveExclusive
spin5()
spin(5)
profile/cProfile
$ python -m profile script.py
$ python -m cProfile script.py
396029 function calls in 3.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 3.000 3.000 script.py:1(<module>)
1 0.000 0.000 1.000 1.000 script.py:10(alice)
1 0.000 0.000 2.000 2.000 script.py:14(bob)
2 1.074 0.537 3.000 1.500 script.py:4(carol)
1 0.000 0.000 0.000 0.000 {method 'disable' of ...
396023 1.926 0.000 1.926 0.000 {time.time}
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 3.000 3.000 script.py:1(<module>)
1 0.000 0.000 1.000 1.000 script.py:10(alice)
1 0.000 0.000 2.000 2.000 script.py:14(bob)
2 1.074 0.537 3.000 1.500 script.py:4(carol)
1 0.000 0.000 0.000 0.000 {method 'disable' of ...
396023 1.926 0.000 1.926 0.000 {time.time}
Exclusive Inclusive
gevent와 잘 붙나?
gevent
• 코루틴 기반 네트워킹 라이브러리
• 경량 스레드: Greenlet
참고: <제약을 넘어: Gevent> ― 정민영 • PyCon KR 2014
https://goo.gl/h0OmE7
서브루틴 코루틴
# gevent-example.py
import gevent
def slower():
for x in range(4):
for y in range(10000000): pass
gevent.sleep(0)
def faster():
for x in range(2):
for y in range(10000000): pass
gevent.sleep(0)
gevent.spawn(slower)
gevent.spawn(faster)
gevent.wait()
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/gevent_example.py
기대 결과
slower > faster
(약 2배)
$ python -m profile gevent-example.py
Traceback (most recent call last):
...
File ".../profile.py", line 211, in trace_dispatch
if self.dispatch[event](self, frame,t):
File ".../profile.py", line 283, in trace_dispatch_call
frame, frame.f_back)
AssertionError: ('Bad call', ('.../gevent/hub.py', 386, 'switch'), ...)
$ python -m cProfile gevent-example.py
함수 Exclusive Inclusive
slower 0.129 0.645
faster 0.340 0.341
profile/cProfile
gevent 호환 안 됨
Yappi
Yet Another Python Profiler
Yappi
• 멀티스레딩 지원
• 스레드 별 CPU 시간 측정
• 빠름 (적은 분석 오버헤드)
$ python -m yappi script.py
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
script.py:1 <module> 1 0.000018 1.815449 1.815449
script.py:4 foo 2 0.288737 1.815422 0.907711
script.py:14 bar 1 0.000004 1.001799 1.001799
script.py:10 baz 1 0.000005 0.813632 0.813632
name tid ttot scnt
_MainThread 139638664439616 1.816491 1
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
script.py:1 <module> 1 0.000018 1.815449 1.815449
script.py:4 foo 2 0.288737 1.815422 0.907711
script.py:14 bar 1 0.000004 1.001799 1.001799
script.py:10 baz 1 0.000005 0.813632 0.813632
name tid ttot scnt
_MainThread 139638664439616 1.816491 1
Exclusive Inclusive
# gevent-example.py
import gevent
def slower():
for x in range(4):
for y in range(10000000): pass
gevent.sleep(0)
def faster():
for x in range(2):
for y in range(10000000): pass
gevent.sleep(0)
gevent.spawn(slower)
gevent.spawn(faster)
gevent.wait()
slower < faster
(약 2배)
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/gevent_example.py
함수 Exclusive Inclusive
slower 0.105112 0.641017
faster 0.105722 0.430493
???
스레드 식별자 설정
set_context_id_callback( f )
f → 스레드 Id
set_context_name_callback( f )
f → 스레드 타입 이름
# gyappi.py
from gevent import getcurrent
import yappi
@yappi.set_context_id_callback
def current_greenlet_id():
return id(getcurrent())
@yappi.set_context_name_callback
def current_greenlet_name():
return type(getcurrent()).__name__
yappi.main()
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/gyappi.py
$ python -m gyappi gevent-example.py
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
gevent-example.py:1 <module> 1 0.000047 0.660959 0.660959
gevent-example.py:3 slower 1 0.421846 0.637329 0.637329
gevent-example.py:8 faster 1 0.214511 0.425404 0.425404
name tid ttot scnt
_getframe 139755003744080 0.674929 2
Hub 139754981772656 0.670471 8
Greenlet 139754981772816 0.660287 5
Greenlet 139754981773136 0.534862 3
함수 Exclusive Inclusive
slower 0.421846 0.637329
faster 0.214511 0.425404
Yappi
gevent 호환 잘 됨
프로그램 시작
프로그램 끝
측정
분석결과 출력
측정…
측정…
분석결과 출력
측정
측정
측정
측정
분석결과 출력
분석결과 출력
분석결과 출력
분석결과 출력
측정
측정
측정
측정
뷰어
분석결과
분석결과
분석결과
분석결과
Yappi 뷰어
Yappi 뷰어
2
3
1
로깅
문자열
처리 난수
발생기
게임로직
보편적으로 불리는 함수가
병목처럼 보임
콜스택 계층구조 보존
로깅로깅 ≠
직접 만들어보자
Profiling
• Unity3D 프로파일러
요구사항
콜스택 계층구조 보존
gevent 호환
대화식 뷰어
실시간 프로파일링
함수 실행 추적
sys.setprofile( f )
threading.setprofile( f )
def spin(sec):
f (<frame of spin>, 'call', ...)
t = time.time()
while time.time() - t < sec:
pass
f (<frame of spin>, 'return', ...)
def spin5():
f (<frame of spin5>, 'call', ...)
spin(5)
f (<frame of spin5>, 'return', ...)
frame
• f_back
• f_code
• f_lineno
상위 frame
code
• co_name
• co_filename
<frame>
frame
• f_back
• f_code
• f_lineno
frame
frame
frame
top-frame
stack = deque()
while frame is not None:
stack.appendleft(frame)
frame = frame.f_back
요구사항
콜스택 계층구조 보존
gevent 호환
대화식 뷰어
실시간 프로파일링
Greenlet 문맥 추적
greenlet.settrace( f )
# gevent-example.py
import gevent
def slower():
for x in range(4):
for y in range(10000000): pass
gevent.sleep(0)
def faster():
for x in range(2):
for y in range(10000000): pass
gevent.sleep(0)
gevent.spawn(slower)
gevent.spawn(faster)
gevent.wait()
slower < faster
(약 2배)
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/gevent_example.py
slowerHub faster
slowerHub faster
f ('switch', (Hub, slower))
f ('switch', (slower, Hub))
f ('switch', (Hub, faster))
f ('switch', (faster, Hub))
f ('switch', (Hub, slower))
slower faster
함수 Exclusive Inclusive
slower 0.424 0.641
faster 0.212 0.429
요구사항
콜스택 계층구조 보존
gevent 호환
대화식 뷰어
실시간 프로파일링
대화식 뷰어는
TUI로
Curses
Urwid
• TUI 라이브러리
• 위젯 단위
• TreeListBox
• Curses의 저주에서 구원
Exclusive Inclusive
Profiling OWN DEEP
Yappi tsub ttot
profile tottime cumtime
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/interactive.gif
요구사항
콜스택 계층구조 보존
gevent 호환
대화식 뷰어
실시간 프로파일링
뷰어
Pickle
프로그램 프로파일링 서버
측정
측정
측정
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/continuous.gif
요구사항
콜스택 계층구조 보존
gevent 호환
대화식 뷰어
실시간 프로파일링
느리다!
특히 YAML 파싱
def f(): pass
def shallow(n):
for x in range(n):
f()
def deep(n):
if n != 0:
deep(n - 1)
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/subcalls_example.py
def f(): pass
def shallow(n):
for x in range(n):
f()
def deep(n):
if n != 0:
deep(n - 1)
def f(): pass
def shallow(n):
for x in range(n):
f()
def deep(n):
if n != 0:
deep(n - 1)
shallow(100) deep(100)
그냥 실행 0.01 0.02
Profiling 2.27 6.07
Yappi 0.07 0.07
cProfile 0.60 0.61
profile 1.26 1.25
(단위: ㎳)
http://goo.gl/Wjc7Jc
통계적 프로파일링
이벤트 기반 프로파일링
• 실행 시간, 호출 횟수 측정
• 이벤트 전수 조사
e.g. profile, hotshot, Yappi, line_profiler
통계적 프로파일링
• 상대적 실행 빈도 측정
• 표본 조사
e.g. pyinstrument, plop, pprofile
통계적 프로파일링
• 샘플링 할 때 실행 중이던 콜스택을 조사
• Exclusive Count ― 콜스택 말단이던 횟수
• Inclusive Count ― 콜스택에 속했던 횟수
def spin(sec):
t = time.time()
while time.time() - t < sec:
pass
def spin5():
spin(5)
spin5()
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/spin_example.py
spin5()
spin(5)
Exclusive Count = 1
Inclusive Count = 1
Inclusive Count = 2
Inclusive Count = 3
Inclusive Count = 4
Exclusive Count = 3
Inclusive Count = 3
Exclusive Count = 2
Inclusive Count = 2
Exclusive Count = 1
Inclusive Count = 1
함수 Exclusive % Inclusive %
spin5 25% (1/4) 100% (4/4)
spin 75% (3/4) 75% (3/4)
요구사항 #2
이벤트 기반 프로파일링
통계적 프로파일링
표본 얻기
sys._current_frames()
스레드 별 <frame> dict
주기적 조사
signal.setitimer(ITIMER_PROF, t, t)
CPU 시간이 t만큼 흐를 때마다 SIGPROF 발동
시그널
• time.sleep()을 끊는다.
• IOError: EINTR
• thread.join() 중엔 발동하지 않는다.
pyinstrument가 쓰는
다른 방법
sys.setprofile(f)
threading.setprofile(f)
추적 함수에서 시간을 재다가 때가 되면 조사
앞 방법 보단 느리다.