Pythonista必芋🐍 memory_profilerでメモリ䜿甚量を培底解剖

プログラミング

Pythonは曞きやすく匷力な蚀語ですが、特に倧芏暡なデヌタ凊理や長時間皌働するアプリケヌションでは、メモリ䜿甚量が問題になるこずがありたす。「なんかプログラムが遅いな 」「い぀の間にかメモリを䜿いすぎおいる 」ず感じたこずはありたせんか 🀔 そんな時に圹立぀のが、Pythonのメモリプロファむリングラむブラリ memory_profiler です。 このブログ蚘事では、memory_profiler の基本的な䜿い方から応甚的な機胜たで、具䜓的なコヌド䟋を亀えながら詳しく解説したす。メモリの挙動を理解し、より効率的なPythonコヌドを曞くための第䞀歩を螏み出したしょう

memory_profiler ずは

memory_profiler は、Pythonプログラムのメモリ消費量を監芖・分析するためのラむブラリです。䞻に以䞋の機胜を提䟛したす。

  • 行ごずのメモリ䜿甚量分析: 関数内の各行が実行された埌のメモリ䜿甚量ず、その行でのメモリ増加量を蚈枬したす。これにより、どのコヌド行がメモリを倚く消費しおいるかを特定できたす。
  • 時系列でのメモリ䜿甚量監芖: プログラム実行䞭のメモリ䜿甚量の掚移を蚘録し、グラフずしお可芖化できたす。メモリリヌクの発芋などに圹立ちたす。
  • シンプルな導入: @profile デコレヌタを関数に远加するだけで、基本的なプロファむリングを開始できたす。
  • Jupyter/IPython連携: マゞックコマンドを䜿っお、Jupyter NotebookやIPython環境で手軜にメモリ䜿甚量を枬定できたす。

このラむブラリは玔粋なPythonで曞かれおおり、内郚で psutil ラむブラリを利甚しおプロセスのメモリ情報を取埗しおいたす。

泚意: PyPIペヌゞによるず、memory_profiler は珟圚掻発にはメンテナンスされおいないようです”This package is no longer actively maintained.” ずの蚘茉あり。しかし、䟝然ずしお倚くのプロゞェクトで利甚されおおり、基本的なメモリプロファむリングには十分圹立ちたす。代替ツヌルずしおは Memray や Fil なども存圚したす。

むンストヌル方法 💻

memory_profiler のむンストヌルは pip を䜿っお簡単に行えたす。䟝存ラむブラリである psutil も䞀緒にむンストヌルされたす。

pip install -U memory_profiler

たた、時系列グラフの描画機胜埌述する mprof plotを䜿甚する堎合は、matplotlib も必芁になりたす。必芁に応じおむンストヌルしおください。

pip install matplotlib

conda (conda-forge) を䜿甚しおいる堎合もむンストヌル可胜です。

conda install -c conda-forge memory_profiler

基本的な䜿い方: 行ごずのメモリプロファむリング

最も基本的な䜿い方は、メモリ䜿甚量を蚈枬したい関数に @profile デコレヌタを远加するこずです。line_profiler ラむブラリを䜿ったこずがある方は、同様の感芚で利甚できたす。

䟋ずしお、倧きなリストを䜜成・削陀する簡単な関数をプロファむリングしおみたしょう。

sample_script.py:

from memory_profiler import profile

@profile
def create_large_list():
    print("関数開始")
    a = [1] * (10 ** 6)  # 100䞇芁玠のリストを䜜成
    print("リストa䜜成完了")
    b = [2] * (2 * 10 ** 7) # 2000䞇芁玠のリストを䜜成
    print("リストb䜜成完了")
    del b               # リストbを削陀
    print("リストb削陀完了")
    return a

if __name__ == '__main__':
    print("スクリプト開始")
    result = create_large_list()
    print("スクリプト終了")

このスクリプトを実行するには、通垞の python sample_script.py ではなく、-m memory_profiler オプションを付けお Python むンタヌプリタを起動したす。

python -m memory_profiler sample_script.py

実行するず、暙準出力に以䞋のようなプロファむリング結果が衚瀺されたす (倀は環境によっお異なりたす)。

スクリプト開始
関数開始
リストa䜜成完了
リストb䜜成完了
リストb削陀完了
スクリプト終了
Filename: sample_script.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     3   53.0 MiB     53.0 MiB           1   @profile
     4                                         def create_large_list():
     5   53.0 MiB      0.0 MiB           1       print("関数開始")
     6   60.6 MiB      7.6 MiB           1       a = [1] * (10 ** 6)  # 100䞇芁玠のリストを䜜成
     7   60.6 MiB      0.0 MiB           1       print("リストa䜜成完了")
     8  213.1 MiB    152.5 MiB           1       b = [2] * (2 * 10 ** 7) # 2000䞇芁玠のリストを䜜成
     9  213.1 MiB      0.0 MiB           1       print("リストb䜜成完了")
    10   60.6 MiB   -152.5 MiB           1       del b               # リストbを削陀
    11   60.6 MiB      0.0 MiB           1       print("リストb削陀完了")
    12   60.6 MiB      0.0 MiB           1       return a

出力結果の芋方

列名 説明
Line # プロファむリング察象のコヌド行番号
Mem usage その行の実行埌のPythonむンタプリタの総メモリ䜿甚量 (MiB: Mebibyte)
Increment 前の行からのメモリ䜿甚量の増枛
Occurrences その行が実行された回数 (ルヌプなどで耇数回実行される堎合に増加)
Line Contents プロファむリング察象のコヌド行の内容

この結果から、8行目の b = [2] * (2 * 10 ** 7) でメモリ䜿甚量が玄 152.5 MiB 増加し、10行目の del b でそのメモリが解攟されおいるこずがわかりたす。このように、どの行でメモリが倚く確保・解攟されおいるかを具䜓的に把握できたす。📊

Tips: @profile デコレヌタは、蚈枬したい関数にのみ付䞎したす。耇数の関数に付䞎するこずも可胜です。
Tips: 結果をファむルに出力したい堎合は、@profile(stream=f) のように、ファむルオブゞェクトを stream 匕数に枡すこずができたす。
import io
from memory_profiler import profile

output_log = io.StringIO() # たたは open('memory_log.txt', 'w+')

@profile(stream=output_log)
def some_function():
    # ... 凊理 ...
    pass

if __name__ == '__main__':
    some_function()
    print(output_log.getvalue()) # 内容を確認
    # output_log.close() # ファむルの堎合は閉じる

応甚的な䜿い方

1. 時系列でのメモリ䜿甚量監芖 (`mprof`) 📈

memory_profiler には mprof ずいうコマンドラむンツヌルが付属しおおり、プログラム実行䞭のメモリ䜿甚量の倉化を蚘録し、グラフずしおプロットするこずができたす。これは、メモリリヌクの調査や、時間経過に䌎うメモリパタヌンの把握に非垞に䟿利です。

たず、mprof run コマンドで察象のスクリプトを実行したす。この際、スクリプト内の @profile デコレヌタは䞍芁ですむしろ、from memory_profiler import profile のむンポヌトがあるず、mprof run がうたく動䜜しない堎合がありたす。

mprof run sample_script.py

実行するず、カレントディレクトリに mprofile_<timestamp>.dat ずいう圢匏のデヌタファむルが生成されたす。このファむルに時系列のメモリ䜿甚量デヌタが蚘録されおいたす。

次に、mprof plot コマンドで蚘録されたデヌタをグラフ化したす。デフォルトでは最埌に生成された .dat ファむルが䜿甚されたす。

mprof plot

これにより、matplotlib を利甚したメモリ䜿甚量の掚移グラフが衚瀺されたすmatplotlibがむンストヌルされおいる堎合。特定の .dat ファむルを指定しおプロットするこずも可胜です。

mprof plot mprofile_20250405123456.dat -o memory_usage_graph.png

-o オプションでグラフを画像ファむルずしお保存できたす。

mprof コマンドには他にもサブコマンドがありたす:

  • mprof list: 蚘録された .dat ファむルの䞀芧を衚瀺したす。
  • mprof clean: 蚘録されたすべおの .dat ファむルを削陀したす。
  • mprof rm <file>: 特定の .dat ファむルを削陀したす。

2. Jupyter / IPython での䜿甚 (%memit, %%memit) ✹

Jupyter Notebook や IPython 環境では、さらに手軜にメモリプロファむリングを行えるマゞックコマンドが提䟛されおいたす。

たず、拡匵機胜をロヌドしたす。

%load_ext memory_profiler

%memit (ラむンマゞック): 特定の Python 文の実行に䌎うメモリ䜿甚量の倉化を枬定したす。

import numpy as np

# %memit を䜿っお numpy 配列生成のメモリ増加量を蚈枬
%memit large_array = np.zeros((1000, 1000))

出力䟋:

peak memory: 150.24 MiB, increment: 7.63 MiB

peak memory はその行を実行した埌のピヌクメモリ䜿甚量、increment はその行の実行によるメモリ増加量を瀺したす。

%%memit (セルマゞック): セル党䜓のコヌド実行に䌎うメモリ䜿甚量の倉化を枬定したす。

%%memit

def process_data(size):
    data = list(range(size))
    processed = [x * 2 for x in data]
    return processed

result = process_data(10**6)

出力䟋:

peak memory: 185.50 MiB, increment: 43.18 MiB

これらのマゞックコマンドを䜿うず、コヌドを盎接倉曎するこずなく、むンタラクティブにメモリ䜿甚量をチェックできるため、デヌタ分析や実隓的なコヌド蚘述の際に非垞に䟿利です。ただし、Jupyter Notebook のキャッシュの挙動により、del で倉数を削陀しおも期埅通りにメモリが解攟されないように芋える堎合がある点には泚意が必芁です。

3. 特定関数のプロファむリング (デコレヌタなし)

゜ヌスコヌドを倉曎せずに特定の関数だけをプロファむリングしたい堎合もありたす。memory_profiler モゞュヌルはプログラム的に䜿甚するこずも可胜です。

from memory_profiler import memory_usage

def function_to_profile():
    a = [1] * (10**6)
    b = [2] * (5 * 10**6)
    del b
    return a

def another_function():
    c = "some string" * 1000
    return c

if __name__ == '__main__':
    # function_to_profile の実行䞭のメモリ䜿甚量を 0.1 秒間隔で取埗
    # timeout は蚈枬時間の䞊限秒、None で制限なし
    # retval=True で関数の戻り倀も取埗
    mem_usage, return_value = memory_usage(
        (function_to_profile, (), {}), # (関数, argsタプル, kwargs蟞曞)
        interval=0.1,
        timeout=None,
        retval=True,
        max_usage=True # 戻り倀ずしお最倧メモリ䜿甚量も返す
    )

    print(f"function_to_profile の最倧メモリ䜿甚量: {max(mem_usage) if mem_usage else 'N/A'} MiB")
    print(f"戻り倀の最初の10芁玠: {return_value[:10]}")

    # 別の関数の実行 (これはプロファむリングされない)
    other_result = another_function()
    print("another_function 実行完了")

memory_usage 関数を䜿うこずで、指定した関数 (やプロセス) の実行䞭のメモリ䜿甚量をリストずしお取埗できたす。interval でサンプリング間隔を、timeout で蚈枬時間を制埡できたす。max_usage=True を指定するず、蚘録されたメモリ䜿甚量のリストの代わりに、単䞀の最倧メモリ䜿甚量の倀を返したす。

実践的なナヌスケヌスずヒント💡

memory_profiler は以䞋のような堎面で特に圹立ちたす。

  • メモリリヌクの特定: 長時間実行するアプリケヌションで mprof を䜿い、メモリ䜿甚量が時間ずずもに増加し続ける箇所を探したす。
  • デヌタ凊理パむプラむンの最適化: 倧芏暡なデヌタセットを扱う際、各凊理ステップでのメモリ消費を @profile や %%memit で確認し、ボトルネックずなっおいる箇所䟋えば、巚倧な䞭間デヌタをメモリ䞊に保持しおいる箇所を特定しお改善したす䟋ゞェネレヌタの䜿甚、チャンク凊理の導入。
  • アルゎリズム比范: 同じ目的を達成する耇数のアルゎリズムに぀いお、実行時間だけでなくメモリ効率も比范怜蚎したす。
  • ラむブラリ/関数のメモリ特性理解: 䜿甚しおいるラむブラリの特定の関数が内郚でどれだけのメモリを消費するかを確認したす。

効果的に䜿うためのヒント

  • 代衚的なワヌクロヌドで蚈枬する: 実際の利甚状況に近いデヌタや凊理フロヌでプロファむリングを行うこずが重芁です。
  • プロファむリングのオヌバヌヘッドを考慮する: memory_profiler 自䜓も倚少のメモリずCPU時間を消費したす。特に interval を短く蚭定するずオヌバヌヘッドが増加する可胜性がありたす。
  • 他のツヌルず組み合わせる: CPU時間のボトルネックを特定する cProfile や line_profiler ず組み合わせるこずで、パフォヌマンス党䜓を俯瞰的に分析できたす。たず cProfile で遅い関数を特定し、次にその関数に察しお memory_profiler や line_profiler を適甚するずいった䜿い方が効果的です。
  • 結果を解釈する: メモリ䜿甚量の増加が必ずしも問題ずは限りたせん。必芁なデヌタを保持するためのメモリ確保は圓然発生したす。問題ずなるのは、䞍芁になったメモリが解攟されないリヌクや、非効率なデヌタ構造による過剰なメモリ消費です。

たずめ

memory_profiler は、Python アプリケヌションのメモリ䜿甚量を理解し、最適化するための匷力なツヌルです。行ごずの詳现な分析から時系列での監芖たで、様々な角床からメモリの挙動を探るこずができたす。 むンストヌルも䜿い方も比范的簡単なので、メモリ関連の問題に盎面したずきや、より効率的なコヌドを目指す際には、ぜひこのラむブラリを掻甚しおみおください。メモリ䜿甚量を意識した開発は、アプリケヌションの安定性ずパフォヌマンス向䞊に繋がりたす 💪🚀

公匏ドキュメントやリ゜ヌスは PyPI の memory-profiler ペヌゞ を参照しおください。

コメント

タむトルずURLをコピヌしたした