Python は柔軟性が高く、簡易なコーディングで幅広い操作を行うことができます。
その反面、メモリ消費が比較的多いデメリットがあります。
メモリ賞を改善するためのテクニックとして、__slots__
を利用した手法があります。
目次
__slots__ を利用しない場合のメモリ量
まず比較のため、__slots__ を利用しないコードのメモリ量を計測します。
class SampleClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
instances = [SampleClass(1, 2, 3) for i in range(10_000_000)]
$ /usr/bin/time -f "Elapsed: %E\nMemory : %M [KB]" python3 main.py
Elapsed: 0:09.39
Memory : 1654656 [KB]
引数を3つ保持する SampleClass を 1000万だけリストに追加します。
生成には 10秒ほどを要し、1.65 GB のメモリが消費されています。
__slots__ を利用する場合
__slots__
の定義をすると、次のようになります。
class SampleClass:
__slots__ = ("a", "b", "c")
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
instances = [SampleClass(1, 2, 3) for i in range(10_000_000)]
$ /usr/bin/time -f "Elapsed: %E\nMemory : %M [KB]" python3 main_slots.py
Elapsed: 0:07.15
Memory : 713384 [KB]
__slots__ を指定することで、メモリの消費が 713 [MB] に軽減されています。
副次効果として、時間も 7秒ほどに軽減されています。
解説:__slots__ とは
Python クラスの __dict__ 属性
Python のクラスは、__dict__
という属性を暗黙的に保持します。
これはインスタンスが保持するメンバを、ディクショナリ形式で持っているものになります。
この恩恵として、クラスインスタンスに新しいメンバを動的に割り当てることができるようになっています。
class SampleClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
instance = SampleClass(1, 2, 3)
print(instance.__dict__)
# {'a': 1, 'b': 2, 'c': 3}
instance.d = 4 # 新規メンバを動的に割り当てられる
print(instance.__dict__)
# {'a': 1, 'b': 2, 'c': 3, 'd': 4}
__slots__ でメンバを厳密定義する
__slots__
には、クラスのメンバの一覧を文字列で定義します。
__slots__
を定義することで、クラスインスタンスが __dict__
属性を保持しないようになります。
これによって、クラスの柔軟性が失われる代わりに __dict__
に消費されるメモリを削減することができます。
コードの例として、次のように __dict__
アクセスや新規メンバの割り当てでエラーが発生するようになります。
class SampleClass:
__slots__ = ("a", "b", "c")
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
instance = SampleClass(1, 2, 3)
print(instance.__dict__)
# AttributeError: 'SampleClass' object has no attribute '__dict__'. Did you mean: '__dir__'?
instance.d = 4
# AttributeError: 'SampleClass' object has no attribute 'd'
dataclass での __slots__ 定義
Python 3.10 以降であれば、dataclass でも __slots__ を有効にすることができます。
from dataclasses import dataclass
@dataclass(slots=True)
class SampleClass:
a: int
b: int
c: int
instances = [SampleClass(1, 2, 3) for i in range(10_000_000)]
$ /usr/bin/time -f "Elapsed: %E\nMemory : %M [KB]" python3 main_slots_dataclass.py
Elapsed: 0:06.85
Memory : 717840 [KB]
コメント