Python – プログラムのメモリを計測する

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
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次