Python の実行速度は、C や Rust などのコンパイル言語に比べ劣势と_seen_ されがちですが、冒頭の通り、適切な最適化手法を駆使すれば、大幅な高速化が可能です。以下のテクニックを活用することで、処理時間やメモリ使用量の削減、ひいては 500% に及ぶ性能向上を実現できます。
1. __slots__ を活用したメモリ最適化
Python ではデフォルトでインスタンス属性を __dict__(辞書)で保持しますが、これにより余分なメモリ overhead が生じます。特に多数のオブジェクトを生成するケースでは顕著です。
次のように、__slots__ をクラスに定義することで、辞書の生成を回避し、メモリ効率と属性アクセス速度を向上させます。
from pympler import asizeof
class PersonDict:
def __init__(self, name, age):
self.name = name
self.age = age
class PersonSlots:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
# インスタンス比較実験
unopt = PersonDict("Yuki", 29)
opt = PersonSlots("Yuki", 29)
print(f"辞書版: {asizeof.asizeof(unopt)} bytes")
print(f"slot版: {asizeof.asizeof(opt)} bytes")
# 実行例: 辞書版 520 bytes → slot版 130 bytes(約75%削減)
より精密なベンチマーク実験では、辞書版と slots 版の生成コストを比較しています:
import time, gc
from pympler import asizeof
def benchmark(cls, name, age, n=5000):
gc.collect()
t0 = time.perf_counter()
for _ in range(n):
obj = cls(name, age)
t1 = time.perf_counter()
mem = asizeof.asizeof(obj)
return mem, (t1 - t0) / n * 1e6 # us/obj
dim, dtime = benchmark(PersonDict, "Takeshi", 30)
siz, stime = benchmark(PersonSlots, "Takeshi", 30)
print(f"辞書: {dim:.0f} bytes, 平均 {dtime:.3f} μs")
print(f"slots: {siz:.0f} bytes, 平均 {stime:.3f} μs")
print(f"速度比: {dtime / stime:.2f}×")
実験結果は状況依存ですが、一般に __slots__ を使うと、インスタンス生成・属性アクセスのオーバーヘッドが軽減され、频繁にオブジェクトを作成・破棄するワークロードにおいて顕著な改善が得られます。
2. リスト内包表記でループを最適化
for ループ += list.append より、リスト内包表記の方が C 実装层面でより効率的に設計されており、多くの場合 30–50% の高速化が見込めます。
import time
def squares_loop(n):
res = []
for i in range(1, n + 1):
res.append(i * i)
return res
def squares_comprehension(n):
return [i * i for i in range(1, n + 1)]
N = 2_000_000
t_loop = time.perf_counter()
squares_loop(N)
t_loop = time.perf_counter() - t_loop
t_comp = time.perf_counter()
squares_comprehension(N)
t_comp = time.perf_counter() - t_comp
print(f"ループ: {t_loop:.4f}s, 内包: {t_comp:.4f}s")
print(f"内包表記はループより約 {(t_loop / t_comp):.2f} 倍高速")
また、sum(i*i for i in ...) のようなジェネレータ式を直接渡す手法との性能比较も有効です:
大規模なデータ処理では、中間リストを生成せずに timeframe 計算コードと書ける点が利点です。
3. @functools.lru_cache によるメモ化
再帰や冗長な再計算を伴う関数には、lru_cache を適用して結果をキャッシュすることが有効です。
from functools import lru_cache
import time
def fib_slow(n):
if n < 2:
return n
return fib_slow(n - 1) + fib_slow(n - 2)
@lru_cache(maxsize=None)
def fib_fast(n):
if n < 2:
return n
return fib_fast(n - 1) + fib_fast(n - 2)
t0 = time.perf_counter()
res1 = fib_slow(30)
t1 = time.perf_counter()
res2 = fib_fast(30)
t2 = time.perf_counter()
print(f"未キャッシュ: {t1 - t0:.4f}s → {res1}")
print(f"キャッシュ後: {t2 - t1:.6f}s → {res2}")
# 実際の例: 0.21s → 0.00002s(10,000倍以上差)
maxsize=None または設計されたサイズを設定することで、長期実行プロセスへの適応も可能です。ただし、再帰以外でも、同一入力で頻繁に再計算される関数(例: weighty-network 調達 CGI)では大幅な高速化を実現します。
4. ジェネレータによるリアルタイム・ストリーミング処理
巨大なデータセットを一括メモリー展開せず、必要時 generate するアプローチはメモリ使用量をсерiority に削減し、GC ストールや OOM エラーを回避できます。
import sys
N = 10_000_000
# リスト生成
lst = [i for i in range(N)]
print(f"リスト: {sys.getsizeof(lst)} bytes")
# ジェネレータ
gen = (i for i in range(N))
print(f"ジェネレータ: {sys.getsizeof(gen)} bytes")
# 処理
print("Sum(list):", sum(lst))
print("Sum(gen):", sum(gen))
実際の用途例として、ログ解析スクリプトを例示します:
def stream_log(path, keyword="ERROR"):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
if keyword in line:
yield line.strip()
# カウント途中中断可能、巨大ファイルも全 volume ロード不要
error_lines = list(stream_log("app.log"))
print(len(error_lines), "errors found")
また、データパイプライン構築時に yield from によるステージ結合も可能で、メモリフットプリントを抑えつつ streaming transform を実現できます。
5. ローカル変数優先でスコープ最適化
Python では、スコープ解決のための辞書探索コストが影響し、ローカル変数アクセスはグローバルより高速です。
import time
LIMIT = 1_000_000
# グローバル定義
GLOBAL_CONST = 999
def via_global():
total = 0
for _ in range(LIMIT):
total += GLOBAL_CONST
return total
def via_local():
local_const = 999
total = 0
for _ in range(LIMIT):
total += local_const
return total
t0 = time.perf_counter(); via_global(); t1 = time.perf_counter()
t2 = time.perf_counter(); via_local(); t3 = time.perf_counter()
print(f"グローバル参照: {(t1 - t0):.4f}s")
print(f"ローカル参照: {(t3 - t2):.4f}s")
print(f"差: {(t1 - t0) / (t3 - t2):.2f}倍")
関数内部で頻繁mente 参照する定数(例: アロケーションサイズ、定義域上限値)は、ローカル常量として定義することで、組み込み系処理の基底速度を自然に引き上げられます。
まとめ:オーバーヘッドの「可視化」が鍵
各最適化手法は、それぞれが独立して機能するのではなく、組み合わせることで相乗効果が得られます。たとえば、slot 化されたクラスのインスタンス群をイテレータで処理し、結果をメモ化された関数で加工する構成は、CPU・メモリの双方を効率的に使い切ります。
最重要は、ボトルネックを計測(exception のみで推定せず) し、性能インパクトの大きなターゲットに集中することです。Python のコード可読性・保守性を損なわず、 Where to optimize の判断が可能なのが、ここに示した手法群の強みです。