Pythonでのメモリ使用量を最適化!7つの詳細ガイド

Pythonのコードを書く手書きのノートパソコンとメモリチップPython
この記事は約8分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Pythonを学び始めているあなた、もしくはPythonをより効率的に使いたいと思っている経験者の皆さん、メモリ使用量の管理にはどのように取り組んでいますか?

この記事では、Pythonでのメモリ使用量を最適化する7つの方法を詳しく解説します。

●Pythonとメモリ管理の基本

Pythonは動的な言語で、メモリ管理は内部的に自動化されています。

しかし、大規模なプログラムを扱うときや、限られたリソースで最大限のパフォーマンスを引き出す必要がある場合には、自分でメモリを効率的に管理することが重要になってきます。

●Pythonでのメモリ使用量を確認する方法

Pythonでのメモリ使用量を確認する方法はいくつかあります。

ここでは、”psutil”と”memory_profiler”という二つのライブラリを使用した方法を紹介します。

○サンプルコード1:psutilを使用する方法

psutilはシステムのハードウェアとプロセスの情報を取得するためのライブラリです。

このコードではpsutilを使用して現在のプロセスのメモリ使用量を取得します。

import psutil
import os

def get_memory_usage():
    process = psutil.Process(os.getpid())
    return process.memory_info().rss

print(get_memory_usage())

上記のコードを実行すると、現在のプロセスのメモリ使用量がバイト単位で出力されます。

これにより、現在のメモリ消費量を把握できます。

○サンプルコード2:memory_profilerを使用する方法

memory_profilerはメモリ使用量を行単位で追跡するライブラリです。

ここでは、memory_profilerを使って関数のメモリ使用量を分析します。

from memory_profiler import profile

@profile
def my_function():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == "__main__":
    my_function()

上記のコードを実行すると、関数my_function内でのメモリ使用量の変化が出力されます。

これにより、特定の関数がどれだけのメモリを使用しているか、またそのメモリ使用量が関数内のどの部分で増減しているかを把握できます。

●メモリリークとは何か

メモリリークはプログラムが必要としないメモリを解放しない状態を指します。

これが起こると、プログラムが不必要なメモリを占有し続け、システム全体のパフォーマンスに影響を及ぼすことがあります。

○Pythonにおけるメモリリークの例

Pythonではガベージコレクションが自動的に行われるため、メモリリークは少ないですが、参照サイクルなどの特殊な状況ではメモリリークが発生する可能性があります。

参照サイクルとは、相互に参照し合うオブジェクト群が存在し、その結果としてガベージコレクションが行われない状況を指します。

def create_cycle():
    list = [i for i in range(1000000)]
    list.append(list)

create_cycle()

このコードは100万の要素を持つリストを作成し、そのリスト自体を最後の要素として追加することで参照サイクルを作ります。

●Pythonでのメモリリークの対処法

Pythonではメモリリークの対処法として、gcモジュールやweakrefモジュールが利用できます。

それぞれの使用例を次に示します。

○サンプルコード3:gcモジュールを使用した対処法

gcモジュールはガベージコレクションを制御するためのモジュールです。

この例では、gc.collect()関数を使って明示的にガベージコレクションを行います。

import gc

def create_cycle():
    list = [i for i in range(1000000)]
    list.append(list)

create_cycle()
gc.collect()

上記のコードでは、create_cycle関数の後でgc.collect()を呼び出すことで、メモリリークを防ぎます。gc.collect()は、ガベージコレクションを手動で行うための関数です。

○サンプルコード4:weakrefモジュールを使用した対処法

weakrefモジュールは、オブジェクトへの弱参照をサポートします。

弱参照は、参照カウントを増加させずにオブジェクトを参照することができます。

これにより、メモリリークを防ぐことが可能になります。

import weakref

class MyClass:
    pass

def create_weak_reference():
    instance = MyClass()
    weak_instance = weakref.ref(instance)
    return weak_instance

weak_instance = create_weak_reference()
print(weak_instance())

このコードでは、MyClassのインスタンスに対する弱参照を作成しています。

create_weak_reference関数が終了した時点で、弱参照を除くすべての参照が消失します。

そのため、弱参照を介しても元のインスタンスはすでに解放されているため、print文ではNoneが出力されます。

●Pythonでメモリ使用量を最適化する方法

Pythonでメモリ使用量を最適化する方法としては、データ構造の最適化、関数とオブジェクトの最適化、そして並列処理と非同期処理の最適化があります。

○サンプルコード5:データ構造の最適化

Pythonでは、異なるデータ構造は異なるメモリ使用量を持ちます。

そのため、使用するデータ構造を適切に選ぶことでメモリ使用量を最適化することが可能です。

import sys

list_data = [1] * 1000000
tuple_data = tuple(list_data)

print(sys.getsizeof(list_data))
print(sys.getsizeof(tuple_data))

このコードでは、同じ内容を持つリストとタプルのメモリ使用量を比較しています。

実行すると、タプルの方がリストよりもメモリ使用量が少ないことがわかります。

つまり、要素が変更されないデータはタプルとして保持することでメモリ使用量を節約できます。

○サンプルコード6:関数とオブジェクトの最適化

Pythonでは、大量のオブジェクトを生成するとき、それらのオブジェクトのメモリ使用量が問題となることがあります。

そのような場合、slotsを使用することでメモリ使用量を削減することが可能です。

class WithoutSlots:
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier

class WithSlots:
    __slots__ = ['name', 'identifier']

    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier

without_slots = WithoutSlots('test', 1)
with_slots = WithSlots('test', 1)

print(sys.getsizeof(without_slots))
print(sys.getsizeof(with_slots))

このコードでは、slotsを使用しているクラスと使用していないクラスのメモリ使用量を比較しています。

実行すると、slotsを使用しているクラスのインスタンスの方がメモリ使用量が少ないことがわかります。

○サンプルコード7:並列処理と非同期処理の最適化

Pythonでは、並列処理や非同期処理を使用することで、CPUやIOの待ち時間を最小化し、メモリ効率を改善することができます。

import asyncio

async def main():
    task1 = asyncio.create_task(some_io_bound_task())
    task2 = asyncio.create_task(some_other_io_bound_task())

    await task1
    await task2

asyncio.run(main())

このコードでは、非同期処理を利用して、2つのIOバウンドのタスクを並列に実行しています。

これにより、一つのタスクがIOに待機している間にもう一つのタスクがCPUを使用でき、全体のパフォーマンスを向上させ、必要なメモリを節約することができます。

●Pythonでのメモリ管理の注意点

Pythonでメモリ管理を行う際の注意点として、まずPythonは自動的にガベージコレクションを行うため、ほとんどの場合で明示的にメモリ管理をする必要はありません。

しかし、大規模なデータを扱う場合や、メモリリークの可能性がある場合には、適切なメモリ管理の知識が必要となります。

まとめ

Pythonでのメモリ管理は、自動化されていますが、大規模なプログラムや特定の状況では、自分でメモリを管理することが重要になります。

この記事では、Pythonでのメモリ使用量を確認する方法やメモリリークの対処法、そしてメモリ使用量を最適化する方法について詳しく解説しました。

これらの知識を利用して、より効率的なPythonプログラムを作成してみてください。