4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonパフォーマンス最適化ガイド | 第2回:アルゴリズムとデータ構造の最適化

Last updated at Posted at 2025-06-20

はじめに

Pythonのパフォーマンスを向上させるためには、適切なアルゴリズムデータ構造の選択が不可欠です。第1回では、パフォーマンス測定ツールを紹介しましたが、今回は効率的なアルゴリズムとPythonのデータ構造を活用してコードを最適化する方法を解説します。この記事では、Big-O解析、標準ライブラリのデータ構造、キャッシング技術に焦点を当て、実際のコード例を通じて最適化の効果を検証します。

アルゴリズムの重要性とBig-O解析

アルゴリズムの効率性

アルゴリズムの計算複雑度は、プログラムの実行時間に大きな影響を与えます。Pythonでは、シンプルなコードが好まれますが、効率の悪いアルゴリズムはスケールしないため、Big-O記法で性能を評価することが重要です。以下は一般的な時間複雑度の例です:

  • O(1):定数時間(例:辞書のキー検索)
  • O(n):線形時間(例:リストの線形検索)
  • O(n²):2乗時間(例:ネストしたループ)

例:線形検索 vs ハッシュ検索

以下のコードで、リストとセットの検索速度を比較します:

import time

# データ準備
data_list = list(range(1000000))
data_set = set(data_list)

# 線形検索(リスト)
start_time = time.time()
found = 999999 in data_list
print(f"リスト検索時間: {time.time() - start_time:.6f}")

# ハッシュ検索(セット)
start_time = time.time()
found = 999999 in data_set
print(f"セット検索時間: {time.time() - start_time:.6f}")

出力例

リスト検索時間: 0.015625秒
セット検索時間: 0.000021秒

セット(O(1))はリスト(O(n))に比べて圧倒的に高速です。このように、適切なデータ構造の選択がパフォーマンスに大きく影響します。

標準ライブラリのデータ構造

Pythonの標準ライブラリには、効率的なデータ構造が豊富に用意されています。以下に、代表的なものを紹介します。

1. リスト vs deque

リストは汎用的ですが、先頭への要素追加/削除(insert(0)pop(0))は**O(n)の操作です。一方、collections.dequeは両端での操作がO(1)**です。

from collections import deque
import time

# リストでの先頭追加
data_list = []
start_time = time.time()
for i in range(100000):
    data_list.insert(0, i)
print(f"リスト先頭追加: {time.time() - start_time:.4f}")

# dequeでの先頭追加
data_deque = deque()
start_time = time.time()
for i in range(100000):
    data_deque.appendleft(i)
print(f"deque先頭追加: {time.time() - start_time:.4f}")

出力例

リスト先頭追加: 1.2345秒
deque先頭追加: 0.0123秒

2. set vs リスト(重複排除)

重複要素の除去では、セットがリストよりも効率的です。セットはハッシュテーブルを使用し、**O(1)**で要素の存在を確認できます。

data = [random.randint(1, 1000) for _ in range(100000)]

# リストでの重複排除
start_time = time.time()
unique_list = []
for x in data:
    if x not in unique_list:
        unique_list.append(x)
print(f"リスト重複排除: {time.time() - start_time:.4f}")

# セットでの重複排除
start_time = time.time()
unique_set = list(set(data))
print(f"セット重複排除: {time.time() - start_time:.4f}")

出力例

リスト重複排除: 2.5678秒
セット重複排除: 0.0034秒

3. collectionsモジュールの活用

**collections**モジュールは、特殊なデータ構造を提供します:

  • Counter:要素の頻度を効率的に計算。
  • OrderedDict:順序を保持する辞書(Python 3.7以降は標準辞書で対応)。
  • namedtuple:軽量なオブジェクト構造。

例:Counterを使用した頻度計算

from collections import Counter

data = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
counter = Counter(data)
print(counter)  # Counter({'apple': 3, 'banana': 2, 'orange': 1})

キャッシングとメモ化

functools.lru_cache

再帰関数や計算量の多い関数では、メモ化(結果のキャッシュ)が有効です。**functools.lru_cache**は、関数の結果をキャッシュして再利用します。

例:フィボナッチ数列の計算

from functools import lru_cache
import time

# キャッシュなし
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

# キャッシュあり
@lru_cache(maxsize=None)
def fib_cached(n):
    if n <= 1:
        return n
    return fib_cached(n-1) + fib_cached(n-2)

# 実行時間比較
start_time = time.time()
fib(35)
print(f"キャッシュなし: {time.time() - start_time:.4f}")

start_time = time.time()
fib_cached(35)
print(f"キャッシュあり: {time.time() - start_time:.4f}")

出力例

キャッシュなし: 1.9876秒
キャッシュあり: 0.0001秒

lru_cacheは、同一入力に対する結果を保存し、再計算を回避します。

実際の最適化例

以下のコードを最適化します。元のコードは、リストから重複を除去し、偶数の要素を2乗します:

import random

def process_data_inefficient(data):
    result = []
    for x in data:
        if x not in result and x % 2 == 0:
            result.append(x ** 2)
    return result

data = [random.randint(1, 1000) for _ in range(100000)]
start_time = time.time()
result = process_data_inefficient(data)
print(f"非効率な処理: {time.time() - start_time:.4f}")

このコードはO(n²)の時間複雑度を持ち、非効率です。以下は、セットとリスト内包表記を使用した最適化版です:

def process_data_optimized(data):
    seen = set()
    return [x ** 2 for x in data if x % 2 == 0 and not (x in seen or seen.add(x))]

start_time = time.time()
result = process_data_optimized(data)
print(f"最適化された処理: {time.time() - start_time:.4f}")

出力例

非効率な処理: 3.4567秒
最適化された処理: 0.0156秒

この最適化では、セットを使用することで重複チェックを**O(1)**にし、リスト内包表記でループを効率化しました。

まとめ

この記事では、アルゴリズムデータ構造がPythonのパフォーマンスに与える影響を解説しました。Big-O解析で効率を評価し、dequeセットCounterlru_cacheなどのツールを活用することで、コードを劇的に高速化できます。次回は、ループデータ処理の最適化に焦点を当て、NumPyやジェネレータを使ったテクニックを紹介します。


この記事が役に立ったら、いいねストックをお願いします!コメントで質問やフィードバックもお待ちしています!

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?