Pythonのmemory_profilerを徹底解説! メモリ使用量を可視化して最適化


 

Pythonスクリプトを実行しているときに、「なんだか動作が重いな」「メモリをたくさん消費している気がする…」と感じたことはありませんか? 大規模なデータ処理や長時間稼働するアプリケーションでは、メモリの効率的な使用が非常に重要です。しかし、どこでメモリが消費されているのか特定するのは簡単ではありません。

そんな時に頼りになるのが、Pythonの**メモリ使用量をプロファイリング(分析)するための便利なツールmemory_profiler**です。

この記事では、memory_profilerの基本的な概念から、なぜメモリ最適化が重要なのか、そして実際にPythonスクリプトのメモリ使用量を計測・可視化する手順まで、初心者の方にも分かりやすく徹底的に解説します。memory_profilerをマスターして、メモリ効率の良い、よりパフォーマンスの高いPythonコードを書きましょう!


 

memory_profilerとは? なぜメモリ最適化に使うのか?

 

memory_profilerは、Pythonスクリプトの行ごとのメモリ使用量を測定・分析するためのライブラリです。コードのどの部分が、どれくらいのメモリを消費しているのかを詳細に可視化できるため、メモリリークの特定や最適化のボトルネックを見つけるのに非常に役立ちます。

なぜmemory_profilerを使ってメモリ最適化を行うのでしょうか?

  • メモリリークの特定: プログラムが不要になったメモリを解放せず、時間とともにメモリ使用量が増え続ける「メモリリーク」を検出できます。

  • メモリ消費のボトルネック特定: 特定の関数や処理が予想以上にメモリを消費している箇所を突き止め、最適化の優先順位を判断できます。

  • パフォーマンスの向上: メモリ使用量を削減することで、システムのSWAP領域へのアクセスが減り、結果的にプログラム全体の実行速度が向上することがあります。

  • 安定性の確保: 限りあるメモリ資源を効率的に使うことで、特にサーバーなどの長時間稼働する環境で、メモリ不足によるクラッシュやパフォーマンス低下を防げます。

  • リソースの有効活用: クラウド環境など、使用したリソースに応じて課金されるサービスでは、メモリ使用量の最適化がコスト削減に直結します。

memory_profilerは、Pythonの標準ライブラリではありませんが、pipで簡単にインストールできます。


 

memory_profilerのインストール方法

 

memory_profilerを使うには、まずPCにインストールする必要があります。Pythonのパッケージ管理ツールpipを使って簡単にインストールできます。

  1. コマンドプロンプト(Windows) または ターミナル(macOS/Linux) を開きます。

  2. 以下のコマンドを実行します。

    Bash
     
    pip install memory_profiler
    

インストールが成功したか確認するには、Pythonのインタラクティブシェルでimport memory_profilerと入力し、エラーが出なければOKです。


 

memory_profilerの基本的な使い方

 

memory_profilerの使い方は非常にシンプルです。主にデコレータを使う方法コマンドラインから実行する方法の2つがあります。

 

1. デコレータ (@profile) を使って特定の関数のメモリをプロファイルする

 

最も一般的な使い方です。プロファイルしたい関数の上に@profileデコレータをつけ、python -m memory_profiler your_script.pyでスクリプトを実行します。

Python
 
# example_memory.py として保存
from memory_profiler import profile
import numpy as np

# @profile デコレータを付けて、この関数のメモリ使用量を計測する
@profile
def create_large_list():
    a = [i * 10 for i in range(1_000_000)] # 大きなリストを作成
    b = [str(i) for i in range(1_000_000)] # 別の大きなリスト
    c = np.arange(1_000_000) # NumPy配列

    # 各オブジェクトの参照を保持することで、メモリが解放されないことを示す
    # 何も返さない場合は、関数終了後にローカル変数は解放される
    return a, b, c 

if __name__ == '__main__':
    # 関数の呼び出し。戻り値をどこかに保持しないと、関数終了後にメモリが解放される場合がある
    # 例ではメモリ使用量の差を見るために、あえて戻り値を保持しない
    _ = create_large_list() 
    print("\n--- メモリプロファイル結果を確認してください ---")

 

実行方法

 

上記のコードをexample_memory.pyという名前で保存し、ターミナルで以下のコマンドを実行します。

Bash
 
python -m memory_profiler example_memory.py

 

実行結果例

 

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     5   43.418 MiB   43.418 MiB           1   @profile
     6                                         def create_large_list():
     7   73.910 MiB   30.492 MiB           1       a = [i * 10 for i in range(1_000_000)] # 大きなリストを作成
     8  109.914 MiB   36.004 MiB           1       b = [str(i) for i in range(1_000_000)] # 別の大きなリスト
     9  117.582 MiB    7.668 MiB           1       c = np.arange(1_000_000) # NumPy配列
    10                                         
    11                                             # 各オブジェクトの参照を保持することで、メモリが解放されないことを示す
    12                                             # 何も返さない場合は、関数終了後にローカル変数は解放される
    13  117.582 MiB    0.000 MiB           1       return a, b, c 

--- メモリプロファイル結果を確認してください ---

結果の見方

  • Line #: ソースコードの行番号。

  • Mem usage: その行を実行した時点でのメモリ使用量(MiB単位)。

  • Increment: 前の行からのメモリ使用量の増加量(MiB単位)。この値がメモリ消費のボトルネックを特定する上で最も重要です。

  • Occurrences: その行が何回実行されたか(ループなどで複数回実行される場合)。

  • Line Contents: ソースコードの内容。

この結果から、a, b, cの各リストやNumPy配列がどれくらいのメモリを消費しているかが一目瞭然ですね。Incrementの値が大きい行が、メモリ消費の主な原因となっていることが分かります。

 

2. コマンドラインからスクリプト全体をプロファイルする (-m mprof run)

 

スクリプト全体や、特定の関数にデコレータを追加できない場合に利用します。この方法では、mprofコマンドを使って、時間の経過とともにメモリ使用量がどのように変化するかをグラフで表示できます。

Bash
 
# example_full_script_memory.py として保存
import time
import numpy as np

def process_data_heavy(size):
    data = np.random.rand(size, size)
    return data

def process_data_light(size):
    data = [i for i in range(size)]
    return data

if __name__ == '__main__':
    print("スクリプト開始")
    
    # 処理A: 多くのメモリを消費
    big_data_a = process_data_heavy(2000) # 2000x2000 のNumPy配列
    time.sleep(2) # メモリ使用量が変わる様子を見るため
    del big_data_a # メモリ解放
    print("処理A完了")

    # 処理B: 比較的少ないメモリを消費
    list_data = process_data_light(100_000)
    time.sleep(1)
    del list_data
    print("処理B完了")
    
    time.sleep(1)
    print("スクリプト終了")

 

実行方法

 

  1. 上記のコードをexample_full_script_memory.pyという名前で保存します。

  2. ターミナルで以下のコマンドを実行します。

    Bash
     
    mprof run example_full_script_memory.py
    

    これにより、mprofile_xxxx.datのようなデータファイルが生成されます。

  3. 次に、このデータをグラフで表示します。

    Bash
     
    mprof plot
    

    これにより、Webブラウザでメモリ使用量のグラフが表示され、時間の経過とともにどのようにメモリが変動したかを視覚的に確認できます。


 

メモリ最適化のためのヒント

 

memory_profilerを使ってメモリ消費のボトルネックを特定したら、以下の点を考慮してコードを最適化することを検討しましょう。

  • 不要なオブジェクトの解放: 大きなオブジェクトが不要になったら、delキーワードで明示的に削除したり、スコープから外れるように関数を設計したりすることで、ガベージコレクションによるメモリ解放を促します。

  • ジェネレータの使用: 大規模なリストなどを一度にメモリにロードする代わりに、ジェネレータを使ってデータを逐次処理することで、メモリ使用量を大幅に削減できます。

  • NumPyなどの効率的なライブラリの使用: 数値計算においては、Pythonの組み込みリストよりもNumPy配列の方がメモリ効率が良い場合が多いです。

  • データ構造の選択: 辞書、セット、タプルなど、目的のデータ量とアクセスパターンに最適なデータ構造を選択します。

  • ファイルからの逐次読み込み: 大容量のファイルは、一度に全て読み込むのではなく、行ごとやチャンクごとに読み込んで処理します。

  • キャッシュの利用: 同じ計算を何度も繰り返す場合は、結果をキャッシュすることで、再計算によるメモリ消費を避けます。ただし、キャッシュ自体がメモリを消費するのでバランスが重要です。


 

まとめ

 

memory_profilerは、Pythonコードのメモリ使用量を詳細に分析し、最適化の指針を与えてくれる非常に強力なツールです。

  • Pythonスクリプトの行ごとのメモリ使用量を計測。

  • @profileデコレータで特定の関数を、**mprof run**でスクリプト全体をプロファイル。

  • 結果からメモリ消費のボトルネックIncrementが大きい行)を特定できる。

  • 不要なオブジェクトの解放、ジェネレータ、効率的なライブラリの使用などでメモリを最適化。

メモリ最適化は、特に大規模データ処理や長時間稼働アプリケーションの安定性・パフォーマンス向上に不可欠です。ぜひ今日学んだmemory_profilerの知識を活かして、メモリ効率の良い堅牢なPythonコードを開発してみてください!


 

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座