Python プログラムのメモリ計測方法メモです。
GNU time
Linux の環境があれば、GNU の time コマンドでメモリの使用量を計測することができます。
サンプルコードとして以下を使用します。
整数 0 を長さ 1000万のリストで確保するだけのコードです。
values = [0] * 100_000_000
GNU の time を利用して、次のようにメモリ量を取得することができます。
$ /usr/bin/time -f %M python3 main.py
788912
結果の単位は [KB] です。
この例では 788 MB のメモリが確保されていることがわかります。
-f %M
を指定することで、プロセスが消費した RSS (物理メモリ)の最大値が取得されます。
メモリ消費問題を調査したい場合は、ほぼすべてのケースで参照したいのは RSS かと思います。
なお、この方法ではピークのメモリしか取得することができません。
メモリ使用量の推移を把握したい場合は別の手段をとる必要があります。
注意点
コマンド呼び出し時には /usr/bin/time
とフルパスで記述する必要があります。
time とだけ入力してしまうと、bash 組み込みの time が呼び出されてしまいます。
$ time -f %M python3 main.py
-f: command not found
real 0m1.734s
user 0m0.035s
sys 0m0.062s
標準ライブラリ tracemalloc
標準ライブラリでメモリを計測するために tracemalloc があります。
スナップショットの取得
以下、ある時点のスナップショットを取得する例です。
import tracemalloc
values = [0] * 100_000_000
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:5]:
print(stat)
実行する際には、-X tracemalloc
のオプションを指定します。
$ python3 -X tracemalloc main.py
/home/gari/sandbox/main.py:3: size=763 MiB, count=3, average=254 MiB
<frozen importlib._bootstrap_external>:672: size=909 KiB, count=8625, average=108 B
<frozen importlib._bootstrap>:241: size=132 KiB, count=1124, average=120 B
/usr/lib/python3.10/abc.py:106: size=75.5 KiB, count=348, average=222 B
<unknown>:0: size=26.5 KiB, count=104, average=261 B
tracemalloc では、ファイルごとにメモリが計測されます。
上記の例ではメモリ消費上位5つを出力しています。
main.py で 763 MiB 消費しているのがわかります。
スナップショットの比較
次のように、スナップショットを2つ取得して、その差分を出力することもできます。
import tracemalloc
snapshot1 = tracemalloc.take_snapshot()
values = [0] * 100_000_000
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, "lineno")
for stat in top_stats[:5]:
print(stat)
$ python3 -X tracemalloc main.py
/home/gari/sandbox/main.py:5: size=763 MiB (+763 MiB), count=2 (+2), average=381 MiB
/usr/lib/python3.10/tracemalloc.py:558: size=1560 B (+1504 B), count=27 (+26), average=58 B
/usr/lib/python3.10/tracemalloc.py:423: size=688 B (+688 B), count=5 (+5), average=138 B
/usr/lib/python3.10/abc.py:107: size=16.5 KiB (-672 B), count=94 (-12), average=179 B
/usr/lib/python3.10/tracemalloc.py:560: size=616 B (+616 B), count=3 (+3), average=205 B
結果の見やすさと扱いづらさに難がありますが、標準ライブラリで利用可能である、というメリットがあります。
外部ライブラリ psutil
psutil を用いると、任意時点のプロセスメモリを取得することができます。
まず pip で psutil をインストールします。
$ pip install psutil
以下、計測用コードのサンプルです。
import psutil
process = psutil.Process()
print(process.memory_info().rss / 1024 ** 2, "[MB]")
values = [0] * 100_000_000
print(process.memory_info().rss / 1024 ** 2, "[MB]")
$ python3 main.py
11.48046875 [MB]
774.42578125 [MB]
出力単位は byte なので、1024 の 2乗で割って MB の単位にしています。
Process
メソッドの引数は pid ですが、これを省略することで自プロセスのメモリを計測することができます。
引数に os.getpid()
を指定するのと同じ効果です。
外部ライブラリ memory-profiler
memory-profiler を利用すると、指定メソッドの詳細なメモリ推移を計測することができます。
pip でインストールします。
$ pip install memory-profiler
memory-profiler 計測方法
計測コードのサンプルです。
from memory_profiler import profile
@profile
def func():
values1 = [0] * 100_000_000
values2 = [0] * 100_000_000
func()
@profile
のデコレータを付与したメソッドが実行されると、メモリの解析結果が標準出力に書き出されます。
$ python3 main.py
Filename: /home/gari/sandbox/main.py
Line # Mem usage Increment Occurrences Line Contents
=============================================================
3 19.0 MiB 19.0 MiB 1 @profile
4 def func():
5 782.1 MiB 763.1 MiB 1 values1 = [0] * 100_000_000
6 1545.0 MiB 762.9 MiB 1 values2 = [0] * 100_000_000
二回コールされるメソッドの出力例
“Mem usage” には、その関数内での消費メモリではなく、プロセスの合計消費メモリが出力されます。
また、メソッド呼び出しのたびに出力されますので、何度もコールされるメソッドへのプロファイルは要注意です。
from memory_profiler import profile
@profile
def func():
values1 = [0] * 100_000_000
values2 = [0] * 100_000_000
func()
values = [0] * 100_000_000
func()
$ python3 main.py
Filename: /home/gari/sandbox/main.py
Line # Mem usage Increment Occurrences Line Contents
=============================================================
3 19.2 MiB 19.2 MiB 1 @profile
4 def func():
5 782.1 MiB 762.9 MiB 1 values1 = [0] * 100_000_000
6 1545.2 MiB 763.1 MiB 1 values2 = [0] * 100_000_000
Filename: /home/gari/sandbox/main.py
Line # Mem usage Increment Occurrences Line Contents
=============================================================
3 782.4 MiB 782.4 MiB 1 @profile
4 def func():
5 1545.3 MiB 762.8 MiB 1 values1 = [0] * 100_000_000
6 2308.2 MiB 762.9 MiB 1 values2 = [0] * 100_000_000
コメント