概要
pythonのプロファイルツールである、cProfile, line_profiler, memory_profilerを使ってみます。
python 2.7.8を使用しています。
下記のフィボナッチ数列のプログラムを使って解説していきます。
import sys
n = int(sys.argv[1])
f = sys.argv[2]
@profile
def fib1(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return b
@profile
def fib2(n):
a, b = 0, 1
for i in xrange(n):
a, b = b, a + b
return b
func = { "fib1": fib1,
"fib2": fib2 }
r = func[f](n)
print(r)
cProfile
関数単位でどこにCPUコストがかかっているか調査できるツールです。
使い方
コマンド実行時にcProfileを指定します
結果
以下のようにtottimeを見ると、どの関数で時間がかかっているのかがわかります。
ncalls tottime percall cumtime percall filename:lineno(function)
1 1.045 1.045 13.498 13.498 fib.py:1(<module>)
1 12.432 12.432 12.453 12.453 fib.py:6(fib1)
1 0.022 0.022 0.022 0.022 {range}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
line_profiler
コードの行単位でどこにCPUコストがかかっているか調査できるツールです。
cProfileを使ってもどこに時間がかかっているか分からない場合にline_profilerを使ってみると良いと思います。
ただし、プロファイルを取りたい関数には上のコードにもあるように@profile
デコレータを付与する必要があります。
また、line_profilerを使うと通常の実行時より実行時間が長くかかることは注意しておく必要があります。
使い方
まずはpipでインストールを行います。
pip install line_profiler
kernprofコマンドでプロファイルを実行します。
-lは行単位でプロファイルを出力するオプションです。
-vはstdoutと.lprofファイルの両方に結果を出力するオプションです。
kernprof -l -v fib.py 1000000
.lprofファイルを使って以下のように結果を出力できます。
python -m line_profiler fib.py.lprof
結果
以下のように%Timeをみるとコードのどのラインがどれくらいの割合で時間を使っているのかがわかります。
Total time: 13.3281 s
File: fib.py
Function: fib1 at line 6
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 @profile
7 def fib1(n):
8 1 2.0 2.0 0.0 a, b = 0, 1
9 1000001 466000.0 0.5 3.5 for i in range(n):
10 1000000 12862043.0 12.9 96.5 a, b = b, a + b
11 1 7.0 7.0 0.0 return b
memory_profiler
このツールは上記2つのツールと違いメモリに関するプロファイルツールです。コードのどの部分でどれくらいのメモリを確保しているか確認することができます。
line_profilerと同じで、このツールでもプロファイルを取りたい関数には@profile
デコレータを付与する必要があります。
memory_profilerも通常の実行時より実行時間が長くかかることは注意しておく必要があります。
使い方
まずはpipでインストールを行います。
pip install memory_profiler
pythonコマンド実行時にmemory_profilerモジュールを指定して実行します。
python -m memory_profiler fib_profile.py 1000000 fib1
結果
Mem Usageをみると各ラインでどれだけのメモリを使用しているのかがわかります。
特に9行目のrangeで内部的にリストが生成されるのでメモリ使用量が増加していることがわかります。
Line # Mem usage Increment Line Contents
================================================
6 22.617 MiB 22.617 MiB @profile
7 def fib1(n):
8 22.617 MiB 0.000 MiB a, b = 0, 1
9 54.109 MiB 30.594 MiB for i in range(n):
10 54.109 MiB 0.000 MiB a, b = b, a + b
11 46.477 MiB -7.633 MiB return b
上記コードではrangeを使うfib1, xrangeを使うfib2があるのですが、fib2を実行してみるとxrangeはrangeとは違い内部的にリストを生成しないのでメモリを余分に使っていないことがわかります。
Line # Mem usage Increment Line Contents
================================================
14 22.617 MiB 22.617 MiB @profile
15 def fib2(n):
16 22.617 MiB 0.000 MiB a, b = 0, 1
17 23.066 MiB 0.000 MiB for i in xrange(n):
18 23.066 MiB 0.449 MiB a, b = b, a + b
19 23.066 MiB 0.000 MiB return b```