遅い場所は推測せず測る
目標
性能問題で最も高くつく間違いは、間違った場所を直すことです。目で読んで「ここが遅そうだ」と選ぶと、たいてい外れます。
ここでは、測って、読んで、直して、もう一度測って減ったかを確認します。
用意するもの
/opt/lab/perf/slowapp.py 느린 곳이 어디인지 눈으로는 안 보이는 프로그램
/opt/lab/perf/iowork.py 같은 일을 순차·스레드·프로세스로 해 보는 프로그램
まず、作業ディレクトリに持ってきます。
mkdir -p /root/perf && cp /opt/lab/perf/* /root/perf/ && cd /root/perf
残すもの
01-time.txt real·user·sys
02-tottime.txt 자체 시간 순
03-cumtime.txt 누적 시간 순
04-fix.txt 고치기 전과 후
05-gil.txt CPU 바운드: 순차·스레드·프로세스
06-wait.txt 기다리는 일: 순차·스레드
07-notes.md 왜 그런지
3つの数字は何を分けるのか
slowapp.pyをtimeで測って、real・user・sysを01-time.txtに残し、realがuser+sysより大きいことが何を意味するのかを、1行書いてください。
{ time python3 slowapp.py ; } 2> 01-time.txt
userは自分のコードがCPUを使った時間、sysはカーネルが自分の代わりに使った時間、realは時計で測った時間です。
realがuser+sysより大きければ、その差の分だけ待ったのです。CPUをどれだけ速くしても、その部分は減りません。
最初の行は直す場所ではない
cProfileを自己時間(tottime)順で実行して、上位10行を02-tottime.txtに残し、最も大きい行がなぜ直す場所ではないのかを書いてください。
python3 -m cProfile -s tottime slowapp.py 2>&1 | head -12
一番上はtime.sleepのはずです。ところがそれはCPUを使わずに待った時間なので、コードを速くして減らせるものではありません。
直せるものは、その下にあります。
自分が遅いのか、他を呼ぶのが遅いのか
今度は累積時間(cumtime)順で実行して、03-cumtime.txtに残してください。enrichのtottimeは0に近いのにcumtimeが大きいことが何を意味するのかを、書きます。
python3 -m cProfile -s cumtime slowapp.py 2>&1 | head -12
tottimeはその関数の中で直接使った時間、cumtimeはその関数が呼んだものまで合計した時間です。
tottimeが0なのにcumtimeが大きければ、その関数自体に落ち度はなく、誰を呼ぶかが問題です。直す場所は、呼ばれた側です。
直して測り直す
slowapp.pyをfastapp.pyにコピーして、遅い部分だけを直し、直す前と後の時間を04-fix.txtに残してください。なぜ遅かったのかも、1行書きます。
parseが、文字列を1文字ずつ+=でつなげています。文字列は変更できない値なので、つなげるたびに新しい文字列をまるごと作ります。長さに対して2乗で増えていきます。
''.join(...)やリスト内包表記で、一度に作ればよいのです。
直したあと、同じ方法で測り直してください。測らなければ、直ったかどうかわかりません。
スレッドを増やしても速くならない
iowork.py cpuを逐次・スレッド・プロセスの3通りで実行して05-gil.txtに残し、なぜスレッドでは速くならないのかを書いてください。
for how in seq thread proc; do python3 iowork.py cpu $how; done
スレッドはほとんど変わらず、プロセスだけが速くなるはずです。
Pythonには、一度に1つのスレッドだけがバイトコードを実行できるようにするロック(GIL)があります。計算だけをする作業は、そのロックを手放さないので、スレッドを増やしても、順番に動くのと同じです。プロセスはインタープリターが別なので、そのロックも別です。
同じ道具が逆に働く
今度はiowork.py waitを逐次・スレッドで実行して06-wait.txtに残し、なぜこちらはスレッドで速くなるのかを書いてください。
for how in seq thread; do python3 iowork.py wait $how; done
待っているあいだは、そのロックを手放します。そのため、他のスレッドがそのあいだに仕事をできます。
そのため、「スレッドは速いのか」という質問には、答えがありません。何を待つ作業なのかで分かれます。ネットワーク・ディスク・データベースを待つならスレッドが勝ち、計算ならプロセスが勝ちます。
次に読む人へ
ここで見たものの中から4つ以上を選んで、07-notes.mdにまとめてください。何をしたかではなく、なぜそうなのかを書きます。
数か月後に性能問題に出会った自分が読むと考えてください。「cProfileを実行した」は役に立たず、「tottimeが大きい行がsleepなら、それはCPUの問題ではない。直せるものはその下にある」は役に立ちます。