TT Lab
はじめる
学ぶ 学習パス コース

CPU・メモリリークの見極め

遅い場所は推測せず測る

TT Labで続きを見る

目標

性能問題で最も高くつく間違いは、間違った場所を直すことです。目で読んで「ここが遅そうだ」と選ぶと、たいてい外れます。

ここでは、測って、読んで、直して、もう一度測って減ったかを確認します。

用意するもの

/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の問題ではない。直せるものはその下にある」は役に立ちます。