Upload
-
View
3.923
Download
3
Embed Size (px)
Citation preview
Profiling실시간 대화식 프로파일러
이흥섭 @ 넥슨 • 왓 스튜디오
PyCon KR 2015
이흥섭[email protected] • http://subl.ee/ • sublee
• 한글라이즈http://hangulize.org/
• TrueSkill 파이썬 구현http://trueskill.org/
• Profiling
• <카트라이더 대시>
• <야생의 땅: 듀랑고>http://durango.nexon.com/
Profilingwhat-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
Inclusive Exclusive
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 호환 안 됨
YappiYet 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 = 1Inclusive Count = 1
Inclusive Count = 2
Inclusive Count = 3
Inclusive Count = 4Exclusive Count = 3Inclusive Count = 3
Exclusive Count = 2Inclusive Count = 2
Exclusive Count = 1Inclusive 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)
추적 함수에서 시간을 재다가 때가 되면 조사
앞 방법 보단 느리다.
shallow(100) deep(100)
그냥 실행 0.01 0.02
setitimer로샘플링 0.01 0.02
setprofile로샘플링 0.19 0.22
Profiling 2.27 6.07
Yappi 0.07 0.07
cProfile 0.60 0.61
profile 1.26 1.25
(단위: ㎳)
요구사항 #2
이벤트 기반 프로파일링
통계적 프로파일링
사용법
$ pip install
git+https://github.com/what-studio/profiling.git
$ python -m profiling COMMAND
• profile
• live-profile
• remote-profile
• view
$ python -m profiling
profile SCRIPT
Yappi, profile/cProfile 커맨드 처럼 스크립트 통째로 분석
-T --timer=
• basic
• thread
• greenlet
-S --sampler=
• itimersignal.setitimer
• tracingsys.setprofile, threading.setprofile
통계적 프로파일링
SamplingProfiler
이벤트 기반 프로파일링
TracingProfiler
-T (이벤트 기반 프로파일링)
-S (통계적 프로파일링)
$ python -m profiling
live-profile SCRIPT
스크립트를 돌리면서 실시간 분석
-i 갱신 주기 (기본: 1초)
$ python -m profiling
remote-profile SCRIPT
스크립트를 돌리다가 뷰어가 붙으면 분석
--bind 서버 엔드포인트 (기본: 127.0.0.1:8912)
$ python -m profiling
view HOST:PORT
대화식 뷰어로 분석결과 보기
H
J K
L
Q^C
[ ]
종료
탐색 (포커스 이동)
접기/펼치기
정렬 기준 변경
갱신 중지/재개
조작법
# facweb.py
from flask import Flask
app = Flask(__name__)
@app.route('/<int:n>')
def fac(n):
r = 1
for x in range(2, n + 1):
r *= x
return str(r)
app.run(port=8080)
Py https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/facweb.py
웹 서버
HTTP 클라이언트
프로파일링 뷰어
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/remote.gif
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/remote.gif
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/remote.gif
GIF https://github.com/sublee/pyconkr2015-profiling-resources/blob/master/remote.gif
$ python -m profiling COMMAND
• profile
• live-profile
• remote-profile
• view
what-studio/profiling
마무리
profile cProfile Yappi Profiling
gevent 호환
콜스택
성능 /
완성도
실시간
UX
2.2k • 3회
함께 해요
Issues
Pull requests
@falsetru @dahlia @Parkayun @kxepal@ravipudi @lqez @ivuk @sickill @htch
그밖에
다른 프로파일러들
• line_profiler
• memory_profiler
• pyinstrument
• plop
• pprofile
그밖에
• <Profiling (computer programming)>Wikipedia • http://goo.gl/qeblxo
• <What are Exclusive and Inclusive?>Steve Carroll • http://goo.gl/KCysud
• <What are Exclusive and Inclusive?>Steve Carroll • http://goo.gl/KCysud
• <Beginners Guide to Performance Profiling>MSDN • https://goo.gl/f9Oej9
• <Profiling tools>Alain Leufroy • https://goo.gl/YxcVWF
함께 해요 #2
질의응답
실시간으로 프로파일링 할 때갱신주기 내 함수 호출만 측정되나요?
네, 현재는 그렇습니다. 분명 갱신주기와 측정주기가 같아서 불편한
경우가 있는데요, Unity3D 프로파일러처럼 실시간 분석결과뿐 아니
라 과거의 분석결과도 탐색할 수 있게 해 해소할 계획입니다.
Q.
A.
개발하는 데 얼마나 걸렸나요?
2014년 8월 한 달 동안 TracingProfiler와 뷰어, 서버를 만들
었고, 2015년 6월부터 천천히 두 달, 집중적으로 한 달 동안
SamplingProfiler를 만들었습니다.
Q.
A.
Python 3의 asyncio와 호환되나요?
https://github.com/what-studio/profiling/issues/26
해당 이슈가 올라와 있지만, 아직 확인해보지 못했습니다.
Q.
A.
분석결과에서 특정 함수를 필터링할 수 있나요?
https://github.com/what-studio/profiling/issues/30
지금은 불가능하지만 마침 제안받은 게 있어 구현할 예정입니다.
Q.
A.
C 함수도 분석할 수 있나요?
sys.setprofile의 콜백함수에 C 함수 호출과 반환 이벤트도
c_call, c_return이라는 이름으로 들어옵니다. 따라서 분석할
수 있겠으나 C 함수 실행에 대응하는 파이썬 frame이 없다 보니 다
소 번거로운 점이 있어 미뤄뒀습니다. 현재 TracingProfiler는
C 함수 이벤트를 무시합니다.
파이썬 frame만 조사할 수 있는 SamplingProfiler의 경우 C
함수는 분석하지 못할 것 같습니다.
Q.
A.
deferral 유틸리티를 어디엔 썼고어디엔 안 썼던데 이유가 무엇인가요?
현장에선 Profiler 객체를 with 문에 넣기도 하고 안 넣기도 하
는 이유가 무엇이냐는 질문으로 오해하고, 그 기능이 처음부터 의도한
게 아니라 풀 리퀘스트로 받은 것이라 그런 것 같다고 잘 못 답변 드렸
습니다.
deferral 유틸리티는 with 문이나 try-finally 문이 깊어지
는 게 싫어서 만들었습니다. 필요한 곳에 모두 쓸 것을 의도했으나 실
수로 빼먹은 것 같습니다.
Q.
A.
프로파일러를 프로파일링할 수 있나요?
TracingProfiler 자체의 성능을 분석하기 위해 썼던 방법입니
다.
우선 전달받은 호출/반환 이벤트를 모두 기록해 목록화하는 함수를
sys.setprofile에 등록한 후 예제코드를 실행합니다. 그렇게
모인 이벤트들에 대해 TracingProfiler._profile() 메소
드를 직접 호출하는 코드를 만들고 그걸 프로파일링했습니다.
Q.
A.
사용한 서체
• KoPub돋움체http://www.kopus.org/biz/electronic/font.aspx
• DejaVu Sans Monohttp://dejavu-fonts.org/
• Octiconshttps://octicons.github.com/
• Font Awesomehttp://fontawesome.io/
감사합니다이흥섭 • [email protected]