Pythonのメモリ使用量を計測する:tracemallocモジュールの使い方とボトルネックの特定

Pythonはメモリ管理を自動で行う(ガベージコレクションを持つ)言語ですが、大規模なデータを扱う処理や長時間稼働するサーバーアプリケーションでは、メモリ使用量の肥大化が問題になることがあります。

このような場合、標準ライブラリの tracemalloc モジュールを使用することで、「どのファイルのどの行で、どれだけのメモリが消費されているか」を詳細に追跡・計測することが可能です。

この記事では、tracemalloc を使ってメモリ割り当ての状況をスナップショットとして取得し、メモリ消費の激しい箇所を特定する方法について解説します。

目次

tracemalloc の基本的な使い方

tracemalloc(Trace Memory Allocation)は、メモリブロックの割り当てを追跡するためのモジュールです。基本的な利用手順は以下の通りです。

  1. tracemalloc.start() で追跡を開始する。
  2. 計測したい処理を実行する。
  3. tracemalloc.take_snapshot() でその時点のメモリ割り当て状況(スナップショット)を取得する。
  4. 取得したデータを分析・表示する。

実践的なコード例:リストと辞書のメモリ比較

具体的な例として、大量のデータを持つ「リスト」と「辞書」を作成し、それぞれのメモリ消費量を比較・分析するスクリプトを作成します。

import tracemalloc
import os

def generate_large_list(size):
    """
    指定されたサイズのリストを作成する関数
    """
    return [i for i in range(size)]

def generate_large_dict(size):
    """
    指定されたサイズの辞書を作成する関数
    (キーと値の両方を保持するため、リストよりメモリを消費する傾向がある)
    """
    return {f"key_{i}": i for i in range(size)}

def main():
    # 1. メモリ割り当ての追跡を開始
    print("--- メモリ計測を開始します ---")
    tracemalloc.start()

    # 2. 計測対象の処理を実行
    # 要素数 100,000 のリストを作成
    data_list = generate_large_list(100000)
    
    # 要素数 100,000 の辞書を作成
    data_dict = generate_large_dict(100000)

    # 3. 現在のメモリ状態のスナップショットを取得
    snapshot = tracemalloc.take_snapshot()

    # 4. 統計情報の取得と表示
    # 'lineno' は、ファイル名と行番号ごとにメモリ使用量を集計するオプション
    top_stats = snapshot.statistics('lineno')

    print("\n[メモリ消費量トップ5]")
    for index, stat in enumerate(top_stats[:5], 1):
        # stat オブジェクトには、サイズ(size)、ブロック数(count)、発生箇所(traceback)が含まれる
        
        # サイズを見やすい単位(KiB)に変換
        size_kib = stat.size / 1024
        
        print(f"{index}. {stat.traceback.format()[0]}")
        print(f"   サイズ: {size_kib:.1f} KiB | ブロック数: {stat.count}")

    # 追跡を終了(メモリの解放)
    tracemalloc.stop()

if __name__ == "__main__":
    main()

実行結果の例:

--- メモリ計測を開始します ---

[メモリ消費量トップ5]
1.   File "memory_check.py", line 15
    return {f"key_{i}": i for i in range(size)}
   サイズ: 11824.2 KiB | ブロック数: 199923
2.   File "memory_check.py", line 8
    return [i for i in range(size)]
   サイズ: 3624.1 KiB | ブロック数: 99912
...

結果の分析

出力結果を見ると、どの行のコードがどれだけのメモリを消費しているかが一目瞭然です。

  • 1位: line 15 の辞書生成処理。約 11.8 MiB を消費しています。
  • 2位: line 8 のリスト生成処理。約 3.6 MiB を消費しています。

このように、同じ要素数であっても辞書の方がリストよりも多くのメモリを消費することが確認できます。

現在のメモリ使用量とピーク値の取得

スナップショットを取るほど詳細な分析が必要なく、単に「今どれくらいメモリを使っているか」を知りたい場合は、get_traced_memory() 関数が便利です。

この関数は、(現在の使用量, 追跡開始からの最大使用量) のタプルを返します。

import tracemalloc

# 追跡開始
tracemalloc.start()

# メモリを消費する処理
temp_data = [b"0" * 1024 * 1024 for _ in range(50)] # 約50MB確保

# メモリ使用量の取得
current, peak = tracemalloc.get_traced_memory()

print(f"現在のメモリ使用量: {current / 1024 / 1024:.2f} MiB")
print(f"最大メモリ使用量: {peak / 1024 / 1024:.2f} MiB")

tracemalloc.stop()

実行結果:

現在のメモリ使用量: 50.00 MiB
最大メモリ使用量: 50.00 MiB

まとめ

  • Pythonのメモリ使用量を詳細に調査するには tracemalloc モジュールを使用します。
  • start() で開始し、take_snapshot() で状態を保存し、statistics('lineno') で行ごとの消費量を分析します。
  • 簡易的な計測には get_traced_memory() が有効です。
  • メモリリークの調査や、パフォーマンス・チューニングを行う際に非常に強力なツールとなります。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次