今回は memory_profiler というモジュールを使ってプログラムのメモリ使用量を調べる方法について紹介する。
このブログでは、以前に Python のプロファイラとして profile/cProfile や line_profiler について書いたことがある。 これまでに紹介したこれらのプロファイラは、主に時間計算量の調査が目的となる。 それに対して memory_profiler では、調べる対象は空間計算量となる。
使った環境は以下の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.12.6 BuildVersion: 16G1212 $ python --version Python 3.6.4
下準備
まずは memory_profiler をインストールする。
$ pip install memory_profiler
スクリプトから memory_profiler を使う
まずは最も基本的な、スクリプトから memory_profiler を使う方法について。
memory_profiler では特定の関数のメモリ使用量をプロファイリングするのに @profile デコレータが使える。 例えば次のサンプルコードでは my_func() 関数を @profile デコレータでプロファイル対象としてマークしている。 関数の内容は変数の入った大きなリストを作って、それを del 文で削除するというものになる。
#!/usr/bin/env python # -*- coding: utf-8 -*- from memory_profiler import profile @profile def my_func(): # 整数の入った大きなリストを用意する a = [0] * (2 * 10 ** 7) # 変数を削除する del a # 先ほどより少し小さなリストを用意する b = [0] * (10 ** 6) # 変数を削除する del b def main(): my_func() if __name__ == '__main__': main()
上記に適当な名前をつけたら、あとは普通に Python のスクリプトとして実行するだけ。 すると、標準出力にプロファイル結果が出力される。 出力内容は、左から「プログラムの行数、その行が評価された時点でのメモリ使用量、その行が評価されたことによる使用量の増減、対応するコード」となっている。
$ python example.py 2>/dev/null Filename: example.py Line # Mem usage Increment Line Contents ================================================ 7 36.1 MiB 36.1 MiB @profile 8 def my_func(): 9 # 整数の入った大きなリストを用意する 10 188.7 MiB 152.6 MiB a = [0] * (2 * 10 ** 7) 11 # 変数を削除する 12 36.1 MiB -152.6 MiB del a 13 # 先ほどより少し小さなリストを用意する 14 43.8 MiB 7.6 MiB b = [0] * (10 ** 6) 15 # 変数を削除する 16 43.8 MiB 0.0 MiB del b
上記を見ると興味深いことが分かる。 最初の変数 a は del 文を発行することで GC が実行されたのか、メモリ使用量は減っている。 それに対し変数 b では del 文を発行してもメモリ使用量は変化していない。
IPython から memory_profiler を使う
続いては IPython からアドホックに memory_profiler を使ってみる。 おそらく、実際のプロファイリングではこの方法を使うことが多いだろう。
まずは IPython をインストールしておく。
$ pip install ipython
先ほどと、ほぼ同じ内容のサンプルコードを用意する。 違いは my_func() 関数に @profile デコレータが付与されていないところだ。
#!/usr/bin/env python # -*- coding: utf-8 -*- def my_func(): a = [0] * (2 * 10 ** 7) del a b = [0] * (10 ** 6) del b def main(): my_func() if __name__ == '__main__': main()
上記で用意した example.py を IPython に読み込ませながら起動する。
$ ipython -i example.py
ちなみに、上記で起動と同時にモジュールを読み込ませているのは手順を省くためだけ。 単独で IPython を起動した後に my_func 関数をインポートしても、もちろん構わない。
$ ipython
...
In [1]: from example import my_func
memory_profiler の IPython 拡張を読み込む。
In [1]: %load_ext memory_profiler
あとは %mprun マジックコマンド経由で my_func() 関数を実行する。 これで、先ほどスクリプトから実行したのと同じ内容が得られる。
In [2]: %mprun -f my_func my_func() Filename: /Users/amedama/Documents/temporary/example.py Line # Mem usage Increment Line Contents ================================================ 5 49.2 MiB 49.2 MiB def my_func(): 6 201.8 MiB 152.6 MiB a = [0] * (2 * 10 ** 7) 7 49.2 MiB -152.6 MiB del a 8 49.2 MiB 0.0 MiB b = [0] * (10 ** 6) 9 49.2 MiB 0.0 MiB del b
処理内容が一行で収まるときは %memit マジックコマンドも便利だ。
In [3]: %memit list(range(10 ** 6)) peak memory: 86.92 MiB, increment: 27.92 MiB In [4]: %memit list(range(10 ** 7)) peak memory: 437.59 MiB, increment: 378.59 MiB
プロファイル結果を matplotlib で折れ線グラフにプロットする
memory_profiler は matplotlib と連携してプロファイル結果をプロットする機能もある。
そのために、まずは matplotlib をインストールしておこう。
$ pip install matplotlib
サンプルコードを mprof run
コマンド経由で実行する。
$ mprof run example.py
mprof: Sampling memory every 0.1s
running as a Python program...
実行が完了したら mprof plot
コマンドでプロットされた結果を確認する。
$ mprof plot Using last profile data.
こんな感じで結果が確認できる。
ちなみにプロファイル結果は履歴が残る。
履歴は mprof list
コマンドで確認できる。
$ mprof list 0 mprofile_20180203230956.dat 23:09:56 03/02/2018
インデックスを指定すれば過去の実行結果のプロットが確認できる。
$ mprof plot 0
履歴を削除するにはインデックスを指定して mprof rm
コマンドを実行する。
$ mprof rm 0
あるいは、全ての履歴を削除したいなら mprof clean
コマンドを使っても構わない。
$ mprof clean
めでたしめでたし。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る