Python 実行環境内の変数メモリ使用量を確認する方法

開発過程中、メモリリークや大きなオブジェクトによるパフォーマンス低下を特定するため、現在のネームスペースにある変数のサイズを把握する必要があります。Python 標準の仕組みおよび外部ライブラリを利用し、変数のメモリ占有量を確認するいくつかのアプローチを以下に示します。

標準ライブラリを用いた基本的な確認

最も簡易な方法として、sys モジュールの getsizeof 関数を利用し、グローバル変数を巡回して表示する関数を作成できます。単位の変換 logic を内部に持たせ、閾値以下の変数は除外するように実装します。

import sys

def inspect_variable_usage(min_threshold_kb=1):
    """
    グローバル変数のメモリ使用量を表示する
    :param min_threshold_kb: 表示する最小サイズ(KB)
    """
    scale_factor = 1024  # KB 単位に変換
    
    for var_name in list(globals().keys()):
        if var_name.startswith('_'):
            continue
            
        try:
            obj = globals()[var_name]
            size_bytes = sys.getsizeof(obj)
            size_kb = size_bytes / scale_factor
            
            if size_kb >= min_threshold_kb:
                print(f"{var_name}: {size_kb:.2f} KB")
        except Exception:
            continue

if __name__ == '__main__':
    large_list = [i for i in range(50000)]
    inspect_variable_usage(min_threshold_kb=10)

表形式での出力と排序

単なる打印ではなく、表形式に整形し、メモリ使用量の大きい順に sorting することで、ボトルネックとなっている変数を特定しやすくできます。ここでは MB 単位で表示し、指定された件数までに絞って出力します。

import sys

def display_sorted_memory_usage(limit_count=5, min_size_mb=0.1):
    """
    変数をメモリ使用量の大きい順に表形式で表示
    """
    collected_data = []
    
    for name in globals():
        if name.startswith('_'):
            continue
        
        try:
            target = globals()[name]
            size_mb = sys.getsizeof(target) / (1024 ** 2)
            if size_mb >= min_size_mb:
                collected_data.append((name, size_mb))
        except Exception:
            continue

    # サイズ降順で排序
    collected_data.sort(key=lambda x: x[1], reverse=True)
    
    # ヘッダー表示
    print(f"| {'Variable Name':<40} | {'Memory (MB)':>12} |")
    print("|" + "-" * 42 + "|" + "-" * 14 + "|")
    
    for name, size in collected_data[:limit_count]:
        print(f"| {name:<40} | {size:>12.2f} |")

# 実行例
dataset_a = [0] * 1000000
config_map = {'key': 'value'} * 10000
display_sorted_memory_usage()

コンテナオブジェクトの再帰的なサイズ計算

リスト、辞書、または NumPy や PyTorch のテンソルなど、内部に他のオブジェクトを参照する構造体の場合、getsizeof だけでは参照先のメモリを含めた正確なサイズが得られません。再帰処理を用いて、オブジェクトグラフ全体を巡回し、総メモリ量を算出する関数が必要です。

import sys
import numpy as np
# import torch  # 必要な場合はインポート

def calculate_deep_size(obj, seen_ids=None):
    """
    オブジェクトおよびその参照先の合計メモリサイズを再帰的に計算
    """
    if seen_ids is None:
        seen_ids = set()
    
    obj_id = id(obj)
    if obj_id in seen_ids:
        return 0
    
    seen_ids.add(obj_id)
    base_size = sys.getsizeof(obj)
    
    # NumPy 配列の特殊処理
    if isinstance(obj, np.ndarray):
        return obj.nbytes
    
    # PyTorch テンソルの特殊処理(必要な場合)
    # if isinstance(obj, torch.Tensor):
    #     return obj.numel() * obj.element_size()

    # コンテナ型の再帰計算
    if isinstance(obj, (list, tuple, set)):
        return base_size + sum(calculate_deep_size(item, seen_ids) for item in obj)
    
    if isinstance(obj, dict):
        dict_size = sum(calculate_deep_size(k, seen_ids) for k in obj.keys())
        dict_size += sum(calculate_deep_size(v, seen_ids) for v in obj.values())
        return base_size + dict_size

    return base_size

def report_deep_memory_usage(threshold_mb=1.0):
    print(f"| {'Variable Name':<40} | {'Deep Size (MB)':>14} |")
    print("|" + "-" * 42 + "|" + "-" * 16 + "|")
    
    results = []
    for name in globals():
        if name.startswith('_'):
            continue
        try:
            obj = globals()[name]
            size_bytes = calculate_deep_size(obj)
            size_mb = size_bytes / (1024 ** 2)
            if size_mb >= threshold_mb:
                results.append((name, size_mb))
        except Exception:
            continue
            
    results.sort(key=lambda x: x[1], reverse=True)
    for name, size in results:
        print(f"| {name:<40} | {size:>14.2f} |")

if __name__ == "__main__":
    matrix_data = np.zeros(10000000)
    nested_structure = {'data': [matrix_data] * 2}
    report_deep_memory_usage()

memory_profiler ライブラリの活用

より詳細な行単位のメモリ分析が必要な場合は、memory_profiler というサードパーティ製ライブラリの利用が有効です。これにより、関数内の各行がどのようにメモリを消費しているかを可視化できます。

まず、パッケージをインストールします。

pip install memory_profiler

次に、監視したい関数に @profile デコレータを付与します。このスクリプトを実行すると、コンソール上に各行のメモリ増減が出力されます。

from memory_profiler import profile

@profile
def process_large_data():
    # メモリ使用量の変動を監視したい処理
    data_buffer = []
    for i in range(100000):
        data_buffer.append(i * 2)
    
    filtered = [x for x in data_buffer if x > 50000]
    return filtered

if __name__ == '__main__':
    process_large_data()

実行時には python -m memory_profiler script_name.py コマンドを使用するか、デコレータが有効な環境で実行することで、各行のメモリ使用量(MB)と増分(Increment)を確認することができます。

タグ: Python Memory-Management sys-getsizeof NumPy PyTorch

8月7日 20:54 投稿