memory_profiler on Ubuntu — Pythonメモリリークを行単位で特定

開発環境

この記事のポイント

  • @profile デコレータを付けるだけで、どの行が何 MiB 消費しているかを行単位で表示できます
  • Ubuntu 24.04 では pip install --break-system-packages memory-profiler(PEP 668 対応)
  • 実測:allocate_big_list()data.append() 行が 9.5 MiB を消費していることを特定
  • del しても Increment が 0.0 MiB なのは Python の GC が内部プールに保持するため(仕様どおり)
  • mprof run を使うと、コードを変えずに実行全体のメモリ推移をタイムラインで記録できます

Pythonのプログラムが「どこでメモリを食っているのか」を特定したいとき、print(sys.getsizeof(x)) を並べても全体像は見えません。memory_profiler を使うと、関数の各行で何 MiB 増えたかをそのまま表示できます。

本記事では Ubuntu 24.04 LTS の Docker コンテナで実際にインストールして動かし、その実出力を載せています。バージョン:memory-profiler 0.61.0Python 3.12.3(2026-06-22 実測)。

目次

  1. memory_profiler とは
  2. インストール手順(Ubuntu 24.04 / 22.04)
  3. @profile デコレータで行単位計測
  4. mprof run でタイムライン計測
  5. よくある落とし穴
  6. Ubuntu 22.04 vs 24.04 の差
  7. よくあるエラーと解決策
  8. まとめ

memory_profiler とは

memory_profiler は Python スクリプトのメモリ使用量を行単位でプロファイリングするサードパーティライブラリです。@profile デコレータを付けた関数を実行すると、各行の実行前後の RSS メモリ(MiB)と増分(Increment)が表形式で出力されます。

内部では psutil ライブラリを使ってプロセスのメモリを一定間隔でサンプリングしています。CPUプロファイラの cProfile と似た使い心地で、メモリ版として使えます。

memory_profiler の2つの計測モード(@profile vs mprof run)
memory_profiler の2つの計測モード(@profile vs mprof run)

2つの計測モードがあります。

  • @profile デコレータ:特定の関数を行単位で計測。どの行が重いか即座に分かる
  • mprof run:スクリプト全体を時系列で計測。コードを変えずに使えるのが利点

インストール手順(Ubuntu 24.04 / 22.04)

手順1:Python と pip を確認する

Ubuntu 24.04 には Python 3.12 が標準で入っています。まず環境を確認します。




ubuntu@linuxlab: ~
$ python3 –version
Python 3.12.3
$ python3 -m pip –version
pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)

pip が入っていない場合は sudo apt install python3-pip で追加します。

手順2:memory_profiler をインストールする

Ubuntu 24.04 の pip に関する注意(PEP 668)

Ubuntu 24.04 では、システムの Python 環境を守るため、pip install--break-system-packages フラグが必要です。Docker コンテナや仮想環境(venv)では問題なく動きますが、ホストの Ubuntu 直接では externally-managed-environment エラーが出ます。




ubuntu@linuxlab: ~
# Ubuntu 24.04(ホスト直接の場合)
$ pip install –break-system-packages memory-profiler psutil
Successfully installed memory-profiler-0.61.0 psutil-7.2.2
 
# Ubuntu 22.04 または venv 内
$ pip install memory-profiler psutil
Successfully installed memory-profiler-0.61.0 psutil-7.2.2
memory_profiler インストール実ログ(Ubuntu 24.04 LTS)(実測)
memory_profiler インストール実ログ(Ubuntu 24.04 LTS)(実測)

インストール後、バージョンを確認します。




ubuntu@linuxlab: ~
$ python3 -m pip show memory-profiler
Name: memory-profiler
Version: 0.61.0
Summary: A module for monitoring memory usage of a python program
Author: Fabian Pedregosa
Requires: psutil

@profile デコレータで行単位計測

計測したいコードを書く

メモリを計測したい関数に @profile を付けます。インポートは不要です(実行時に python3 -m memory_profiler 経由で自動注入されます)。




test_leak.py
from memory_profiler import profile
 
@profile
def allocate_big_list():
data = []
for i in range(100000):
data.append(str(i) * 10)
return data
 
@profile
def process_and_free():
big = [b”x” * 1024 for _ in range(5000)]
total = sum(len(b) for b in big)
del big # メモリを解放
return total
 
if __name__ == “__main__”:
result = allocate_big_list()
freed = process_and_free()
print(“processed”, freed, “bytes”)

実行する

python3 test_leak.py ではなく、python3 -m memory_profiler test_leak.py で実行します。




ubuntu@linuxlab: ~
$ python3 -m memory_profiler test_leak.py
Filename: /tmp/test_leak.py
 
Line # Mem usage Increment Occurrences Line Contents
=============================================================
4 21.8 MiB 21.8 MiB 1 @profile
5 def allocate_big_list():
6 21.8 MiB 0.0 MiB 1 data = []
7 31.2 MiB 0.0 MiB 100001 for i in range(100000):
8 31.2 MiB 9.5 MiB 100000 data.append(str(i) * 10)
9 31.2 MiB 0.0 MiB 1 return data
 
11 31.2 MiB 31.2 MiB 1 @profile
12 def process_and_free():
13 31.2 MiB 0.0 MiB 5001 big = [b”x” * 1024 for _ in range(5000)]
14 31.2 MiB 0.0 MiB 10003 total = sum(len(b) for b in big)
15 31.2 MiB 0.0 MiB 1 del big
16 31.2 MiB 0.0 MiB 1 return total
@profile デコレータの実行画面(実測・Ubuntu 24.04)
@profile デコレータの実行画面(実測・Ubuntu 24.04)

出力の読み方

4つの列があります。

列名 意味 実測値の例
Mem usage その行の実行後のプロセス全体の RSS メモリ 31.2 MiB
Increment 前の行からの増分(ここが大きいほど問題) 9.5 MiB
Occurrences その行が実行された回数(ループ回数) 100000
Line Contents ソースコードの内容 data.append(…)

今回の実測では、行 8(data.append(str(i) * 10))が 9.5 MiB の Increment を示しています。10万回のループで文字列をリストに追加する処理がメモリの主な消費源だと分かります。

@profile デコレータ実行結果(行単位メモリ使用量)(実測)
@profile デコレータ実行結果(行単位メモリ使用量)(実測)

行 15 の del big の Increment が 0.0 MiB というのは最初驚きました。「del したのになぜ減らないの?」と思いましたが、これは Python の GC が解放したメモリを OS に返さず内部プールに保持する仕様です。実際のリークかどうかは、プログラム全体を通じて Mem usage が増え続けるかどうかで判断します。

mprof run でタイムライン計測

@profile は特定の関数の内部を見るためのツールですが、「関数をまたいでどこからメモリが増え始めたか」を掴むには mprof コマンドが便利です。コードを一切変えずに使えます。

mprof run の実行




ubuntu@linuxlab: /tmp
$ mprof run mprof_sample.py
mprof: Sampling memory every 0.1s
running new process
running as a Python program…
done
$ ls mprofile_*.dat
mprofile_20260622043355.dat

実行後、mprofile_*.dat ファイルが生成されます。中身は「MEM <使用量MiB> <タイムスタンプ>」の単純なテキスト形式です。

mprof run の実行画面と計測データ(実測・Ubuntu 24.04)
mprof run の実行画面と計測データ(実測・Ubuntu 24.04)

今回の実測では、起動直後 0.5 MiB → grow_memory() 実行中に段階的に増加 → ピーク 29.0 MiB → del 後も 29.0 MiB を維持、という推移が記録されました。

mprof run タイムライン計測結果(実測)
mprof run タイムライン計測結果(実測)

グラフで可視化する(ローカル環境)

ローカルの Ubuntu(表示環境あり)では mprof plot でグラフ表示もできます。




ubuntu@linuxlab: ~
# matplotlib が必要(事前インストール)
$ pip install matplotlib
$ mprof plot
← 最新の .dat ファイルをグラフ表示
$ mprof plot –output memory.png
← PNG として保存(ヘッドレス環境でも使える)

サーバー(ヘッドレス)環境では –output でファイル保存

VPS や Docker コンテナなど表示環境がない場合、mprof plot だけだと「cannot connect to X server」エラーになります。--output memory.png オプションを付けて PNG に保存してから scp 等で手元に転送してください。

よくある落とし穴

①「del したのにメモリが減らない」は正常

実測で確認したように、del 後の Increment は 0.0 MiB でした。Python のメモリ管理では、delで参照を外しても GC がそのメモリを OS に返さず内部の「メモリプール」に保持します。次回の割り当てで再利用されるため、プロセスの RSS は下がらないことが多いです。

本当のリークかどうかは、プログラム実行全体を通じて Mem usage が一方向に増え続けるかどうかで判断します。

②import を忘れると NameError

python3 スクリプト名.py で直接実行した場合、@profile が未定義で NameError になります。必ず python3 -m memory_profiler スクリプト名.py で実行するか、スクリプト先頭に以下のフォールバックを入れます。




script.py の先頭
# memory_profiler なしでも動くフォールバック
try:
from memory_profiler import profile
except ImportError:
def profile(func):
return func

③リスト内包表記の計測は不正確になることがある

big = [b"x" * 1024 for _ in range(5000)] のような1行の内包表記は、「実行中の途中」のメモリが行末時点でサンプリングされるため、実際の最大使用量より低く出ることがあります。詳細が必要な場合は for ループに展開して計測します。

Ubuntu 22.04 vs 24.04 の差

Ubuntu 22.04(Python 3.10.12)と Ubuntu 24.04(Python 3.12.3)で実際に計測した差をまとめます。

Ubuntu 22.04 vs 24.04 バージョン比較(実測)
Ubuntu 22.04 vs 24.04 バージョン比較(実測)

memory-profiler 0.61.0 / psutil 7.2.2 はどちらも同じバージョンです。Python 3.12.3 のほうがインタープリタ自体のベースラインが約 2 MiB 高い(21.8 MiB vs 19.5 MiB)ですが、これは Python 3.12 の内部メモリモデルの変更によるものです。実際のアプリケーションで計測する際は、実行環境に合わせて比較するようにします。

インストールコマンドは 22.04 と 24.04 で異なります。

OS Python pip install コマンド memory-profiler
Ubuntu 22.04 LTS 3.10.12 pip install memory-profiler psutil 0.61.0
Ubuntu 24.04 LTS 3.12.3 pip install --break-system-packages memory-profiler psutil 0.61.0

よくあるエラーと解決策

error: externally-managed-environment




ubuntu@linuxlab: ~
$ pip install memory-profiler
error: externally-managed-environment
× This environment is externally managed

Ubuntu 24.04 での PEP 668 エラーです。--break-system-packages フラグを付けるか、python3 -m venv ~/.venv && source ~/.venv/bin/activate で仮想環境を作ってからインストールします。

ModuleNotFoundError: No module named ‘memory_profiler’

インストール先と実行する Python が合っていない可能性があります。python3 -m pip show memory-profiler で Location を確認し、同じ Python で実行しているか確認します。

No module named ‘psutil’

memory_profiler は psutil に依存しています。pip install psutil を別途実行するか、pip install memory-profiler psutil でまとめてインストールします。

まとめ

memory_profiler を使うと、Python スクリプトのどの行が何 MiB 消費しているかを行単位で確認できます。今回の実測(Ubuntu 24.04 / Python 3.12.3)では、data.append(str(i) * 10) が 9.5 MiB の Increment を記録し、ループ内の文字列操作がメモリ消費の主因だと特定できました。

  • @profile デコレータ + python3 -m memory_profiler スクリプト.py で行単位計測
  • mprof run スクリプト.py でタイムライン計測(コード変更不要)
  • Ubuntu 24.04 では --break-system-packages フラグが必要(venv を使うとフラグ不要)
  • del 後にメモリが減らないのは Python の GC 仕様で、リークではない
  • 本当のリークは Mem usage が一方向に増え続ける場合

VPS で Python サーバーを動かしているとメモリ不足になることがあります。その際は memory_profiler でボトルネックを特定してから最適化するのが効率的です。

コメント

タイトルとURLをコピーしました