pythonのパフォーマンス調査で使えるライブラリの紹介


pythonアプリケーションのパフォーマンス改善をする機会があったので、
その時の調査で役に立ったライブラリの使い方を、なるべく簡単に説明したいと思います。

今回紹介するのは、この3つです。

line_profiler・・・実行速度を計測するライブラリ
memory_profiler・・・メモリ使用量を計測するライブラリ
pdb・・・プログラムのデバッガ

全てIPythonのコンソールで使えるライブラリなので、コンソールで使い方を説明してきたいと思います。

line_profilerの使い方

line_profilerをインストールします。

$ pip install line_profiler

IPythonのコンソールを起動します。
(line_profilerのインストール時にIPythonも同梱されています。)

$ ipython

実行速度を計測するメソッドをコンソール上で作成します。

import time
def hoge():
    # 0.01秒スリープさせる
    time.sleep(0.01)
    # 10000回ループさせる
    for i in range(10000):
        pass

コンソール上でline_profilerのコマンドを使えるようにします。

load_ext line_profiler

line_profilerのlprunコマンドでメソッドの実行速度を計測します。

lprun -f hoge hoge()

実行結果が表示されます。

Timer unit: 1e-06 s

Total time: 0.01848 s
File: <ipython-input-22-ac529d19217f>
Function: hoge at line 2

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     2                                           def hoge():
     3                                               # 0.01秒スリープさせる
     4         1        10064  10064.0     54.5      time.sleep(0.01)
     5                                               # 10000回ループさせる
     6     10001         4574      0.5     24.8      for i in range(10000):
     7     10000         3842      0.4     20.8          pass

当たり前ですが、time.sleepで約0.01秒かかったことがわかります。
ループが10000回行われたこともHits列でわかります。
Time列の数値が高いところが時間のかかった処理ということになるので、そこの処理を見直すと処理速度の改善につながると思います。

引数のあるメソッドの場合は、このように書きます。

import time
def hoge(loop_count):
    # 0.01秒スリープする
    time.sleep(0.01)
    # 引数の数分ループさせる
    for i in range(loop_count):
        pass
lprun -f hoge hoge(10000)

memory_profilerの使い方

memory_profilerをインストールします。

$ pip install memory_profiler

IPythonをインストールします。

$ pip install ipython

メモリ使用量を計測するメソッドを書いたファイルを作成します。
(memory_profilerはコンソール上で作成したメソッドは計測できないので。)

hoge.py

def hoge():
    # 100000個の配列を作る
    l = list()
    for i in range(100000):
        l.append(i)
    # 100000個の辞書を作る
    d = dict()
    for i in range(100000):
        d[i] = None

メソッドを作ったらIPythonのコンソールを起動します
起動時に先ほど作成したhoge.pyファイルを読み込ませます。

$ ipython -i hoge.py

コンソール上でmemory_profilerのコマンドを使えるようにします。

load_ext memory_profiler

memory_profilerのmprunコマンドでメソッドのメモリ使用量を計測します。

mprun -f hoge hoge()

実行結果が表示されます。

Line #    Mem usage    Increment   Line Contents
================================================
     1     34.7 MiB      0.0 MiB   def hoge():
     2                                 # 100000個の配列を作る
     3     34.7 MiB      0.0 MiB       l = list()
     4     37.7 MiB      3.0 MiB       for i in range(100000):
     5     37.7 MiB      0.0 MiB           l.append(i)
     6                                 # 100000個の辞書を作る
     7     37.7 MiB      0.0 MiB       d = dict()
     8     49.1 MiB     11.3 MiB       for i in range(100000):
     9     49.1 MiB      0.0 MiB           d[i] = None

配列の作成に3.0MiB、辞書の作成に11.3MiBのメモリが使われたことがわかりました。

引数のあるメソッドの場合は、このように書きます。

def hoge(loop_count):
    l = list()
    for i in range(loop_count):
        l.append(i)
    d = dict()
    for i in range(loop_count):
        d[i] = None
mprun -f hoge hoge(100000)

pdbの使い方

pdbはpythonに最初から付属されているので特にインストールする必要はありません。
使い方は簡単で、デバッグを開始したい行に以下のコードを挿入するだけです。

import pdb; pdb.set_trace()

コンソール上で適当なメソッドを作って、先ほどのコードを挿入します。

def hoge():
    var = "fuga"
    print (var)
    import pdb; pdb.set_trace()
    print (var)

hogeメソッドを実行すると、pdb.set_trace()で処理が止まり、pdbのコマンド入力待ち状態となります。

In [2]: hoge()
fuga
> <ipython-input-1-3d0c1a13d7af>(5)hoge()
-> print (var)
(Pdb) 

pコマンドで変数varの内容が確認できます。

(Pdb) p var
'fuga'

途中で変数varの内容を変更することもできます。

(Pdb) var = "piyo"
(Pdb) p var
'piyo'

あとは以下のコマンドを使ってステップ実行しながら確認するとデバッグしやすいと思います。

コマンド 説明
s(tep) 現在の行を実行します。実行する行が関数やメソッドの場合ステップインします。
n(ext) 現在の行を実行します。実行する行が関数やメソッドの場合ステップインしません。
c(ontinue) 次のブレークポイント(pdb.set_trace())まで実行を継続します。
l(ist) 現在のソースコードを表示します。

ちなみに、eclipseのPyDevを使うとGUIでデバッグできて便利みたいです。
eclipseを使っていないという人や、サーバーのコンソール上で確認することがある人はpdbを使ってみるといいと思います。