Pythonでシンプルに処理時間を計測するテクニック4選

処理時間計測の徹底解説Python
この記事は約17分で読めます。

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

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

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

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

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

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

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

●Pythonで処理時間を計測する4つの方法

プログラムの処理時間を計測することは、コードの最適化やパフォーマンス改善に欠かせない作業です。

特に、大規模なシステムや複雑なアルゴリズムを扱う場合、少しの処理時間の差が大きな影響を与えることがあります。

Pythonには、処理時間を計測するためのさまざまなモジュールやテクニックが用意されています。

ここでは、初心者にもわかりやすく、すぐに実践できる4つの方法を紹介します。

○timeモジュールを使う

timeモジュールは、Pythonの標準ライブラリの1つで、時間に関する各種関数を提供しています。

処理時間の計測には、time()関数を使います。

□サンプルコード1:timeモジュールの基本的な使い方

import time

start_time = time.time()

# 処理時間を計測したい処理をここに書く
for i in range(1000000):
    pass

end_time = time.time()
elapsed_time = end_time - start_time

print(f"経過時間:{elapsed_time}秒")

このコードでは、time()関数を使って処理の開始時間と終了時間を取得し、その差分から経過時間を計算しています。

実行結果

経過時間:0.04690289497375488秒

この例では、単純なループ処理を1,000,000回実行するのに、約0.047秒かかったことがわかります。

□サンプルコード2:ミリ秒単位での計測

秒単位では詳細な計測ができない場合、ミリ秒単位で計測するのも1つの方法です。

timeモジュールのtime()関数は、デフォルトでは秒単位ですが、結果に1000を掛けることでミリ秒単位に変換できます。

import time

start_time = time.time()

# 処理時間を計測したい処理をここに書く
for i in range(1000):
    pass

end_time = time.time()
elapsed_time_ms = (end_time - start_time) * 1000

print(f"経過時間:{elapsed_time_ms}ミリ秒")

実行結果

経過時間:0.04887580871582031ミリ秒

ループ回数を1,000回に減らしたため、ミリ秒単位でも計測できる程度の処理時間になりました。

timeモジュールは簡単に使えるため、手軽に処理時間を計測したい場合に適しています。

ただし、より詳細な計測や、平均実行時間の算出などが必要な場合は、後述するtimeitモジュールを使うのがおすすめです。

○datetimeモジュールを使う

timeモジュールでは秒単位やミリ秒単位での計測ができましたが、より詳細な時間管理が必要な場合はdatetimeモジュールを使うのがおすすめです。

datetimeモジュールでは、日付や時刻を表すdatetimeオブジェクトを使って、時間の計算や比較を行うことができます。

処理時間の計測には、datetime.now()関数を使います。

この関数は、現在の日時を表すdatetimeオブジェクトを返します。

□サンプルコード3:datetimeモジュールの基本的な使い方

from datetime import datetime

start_time = datetime.now()

# 処理時間を計測したい処理をここに書く
for i in range(1000000):
    pass

end_time = datetime.now()
elapsed_time = end_time - start_time

print(f"経過時間:{elapsed_time}")

実行結果

経過時間:0:00:00.046887

datetime.now()関数で取得した開始時間と終了時間の差分を計算することで、経過時間を求めています。

結果は、時:分:秒.マイクロ秒の形式で表示されます。

□サンプルコード4:経過時間の計算

datetimeオブジェクトは、時間の計算にも使えます。

たとえば、経過時間をより詳細に分析したい場合は、total_seconds()メソッドを使って秒単位の経過時間を取得できます。

from datetime import datetime

start_time = datetime.now()

# 処理時間を計測したい処理をここに書く
for i in range(1000000):
    pass

end_time = datetime.now()
elapsed_time = end_time - start_time

elapsed_seconds = elapsed_time.total_seconds()
elapsed_milliseconds = elapsed_seconds * 1000
elapsed_microseconds = elapsed_seconds * 1000000

print(f"経過時間(秒):{elapsed_seconds}秒")
print(f"経過時間(ミリ秒):{elapsed_milliseconds}ミリ秒")
print(f"経過時間(マイクロ秒):{elapsed_microseconds}マイクロ秒")

実行結果

経過時間(秒):0.04688119888305664秒
経過時間(ミリ秒):46.88119888305664ミリ秒
経過時間(マイクロ秒):46881.19888305664マイクロ秒

このように、total_seconds()メソッドを使えば、経過時間を秒単位で取得できます。

さらに、その結果に1000を掛けることでミリ秒単位に、1000000を掛けることでマイクロ秒単位に変換できます。

○timeitモジュールを使う

ここまで、timeモジュールとdatetimeモジュールを使った処理時間の計測方法を見てきましたが、実は、Pythonにはもっと便利なモジュールがあります。

それが、timeitモジュールです。

timeitモジュールは、小さなコード片の実行時間を測定するために設計されたモジュールで、特に、同じコードを複数回実行して平均実行時間を求めるのに便利です。

□サンプルコード5:timeitモジュールの基本的な使い方

import timeit

code_to_test = '''
for i in range(1000000):
    pass
'''

elapsed_time = timeit.timeit(code_to_test, number=1)

print(f"経過時間:{elapsed_time}秒")

実行結果

経過時間:0.04381070000071116秒

timeit.timeit()関数の第一引数には、実行時間を計測したいコードを文字列として渡します。

ここでは、100万回のループを実行するコードを渡しています。

第二引数のnumberは、コードを実行する回数を指定します。

デフォルトでは100万回の計測が行われますが、ここでは1回に設定しています。

□サンプルコード6:繰り返し実行による平均実行時間の計測

timeitモジュールの真価は、同じコードを複数回実行して平均実行時間を求める場合に発揮されます。

たとえば、先ほどのコードを10回実行して平均実行時間を求めてみましょう。

import timeit

code_to_test = '''
for i in range(1000000):
    pass
'''

elapsed_time = timeit.timeit(code_to_test, number=10)

print(f"経過時間(10回の平均):{elapsed_time / 10}秒")

実行結果

経過時間(10回の平均):0.0440354090001583秒

number=10と指定することで、コードを10回実行し、その合計実行時間を求めています。

その結果を10で割ることで、平均実行時間を計算しています。

○デコレータを使う

最後に紹介するのは、デコレータを使った処理時間の計測方法です。

デコレータは、関数やメソッドの前後に処理を追加するための機能で、Pythonの強力な機能の1つです。

処理時間の計測にデコレータを使うと、計測したい関数やメソッドを修正することなく、簡単に計測を行うことができます。

まずは、デコレータの基本的な使い方から見ていきましょう。

□サンプルコード7:デコレータの基本的な使い方

def my_decorator(func):
    def wrapper(*args, **kwargs):
        # 前処理
        result = func(*args, **kwargs)
        # 後処理
        return result
    return wrapper

@my_decorator
def my_function():
    # 処理内容
    pass

デコレータは、関数を引数に取り、新しい関数を返す関数です。

ここでは、my_decoratorという名前のデコレータを定義しています。

デコレータの内部では、wrapperという関数を定義し、元の関数を呼び出す前後に処理を追加しています。

*args**kwargsは、元の関数に渡された引数をそのままwrapper関数に渡すための記法です。

@my_decoratorという記法を使って、my_function関数にデコレータを適用しています。

これにより、my_function関数が呼び出される前後に、デコレータ内の処理が実行されます。

□サンプルコード8:デコレータを使った処理時間の計測

それでは、デコレータを使って処理時間を計測してみましょう。

import time

def measure_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        elapsed_time = end_time - start_time
        print(f"{func.__name__}の実行時間:{elapsed_time}秒")
        return result
    return wrapper

@measure_time
def my_function():
    for i in range(1000000):
        pass

my_function()

実行結果

my_functionの実行時間:0.05160760879516602秒

measure_timeデコレータの内部では、timeモジュールを使って処理時間を計測しています。

計測したい関数に@measure_timeデコレータを付けるだけで、関数の実行時間を計測することができます。

func.__name__は、デコレータが適用された関数の名前を取得するための記法です。

これを使うことで、どの関数の実行時間を計測したのかを出力することができます。

デコレータを使った処理時間の計測は、コードの修正が少なくて済むため、既存のコードに手軽に計測機能を追加することができます。

また、デコレータを使えば、計測以外にもさまざまな処理を関数の前後に追加することができます。

ただし、デコレータを使いすぎると、コードの可読性が下がる可能性がある点には注意が必要です。

適度な使用にとどめ、コードの意図が伝わりやすいようにしましょう。

●計測時の注意点

ここまで、PythonでCPU時間を計測するさまざまな方法を見てきました。

しかし、正確な計測を行うためには、いくつか注意点があります。

ここでは、計測時に気をつけるべきポイントを3つ紹介します。

○sleepを使ったウェイト処理

処理時間を計測する際、計測対象のコードにsleep関数を使ったウェイト処理が含まれていると、計測結果が大きく影響を受けます。

たとえば、次のようなコードがあったとします。

import time

def my_function():
    time.sleep(1)  # 1秒間のウェイト処理
    for i in range(1000):
        pass

start_time = time.time()
my_function()
end_time = time.time()

print(f"経過時間:{end_time - start_time}秒")

実行結果

経過時間:1.0010743141174316秒

my_function内で1秒間のウェイト処理が行われているため、計測結果は1秒以上になっています。

しかし、このウェイト処理は、実際の処理時間とは関係ありません。

したがって、処理時間を正確に計測するためには、計測対象のコードからsleep関数を使ったウェイト処理を取り除く必要があります。

○計測環境による誤差

処理時間の計測結果は、計測環境によって大きく異なる可能性があります。

たとえば、次のような要因が計測結果に影響を与える可能性があります。

  • マシンのスペック(CPU、メモリ、ストレージなど)
  • OSやPythonのバージョン
  • バックグラウンドで動作しているプロセス
  • 計測時の負荷状況

特に、バックグラウンドで重い処理が動作している場合、計測結果が大きく影響を受ける可能性があります。

したがって、処理時間の計測は、できるだけ同じ環境で行うことが重要です。

また、複数回の計測を行い、平均値を取るなどの工夫も必要でしょう。

○計測対象の適切な選択

処理時間を計測する際は、計測対象を適切に選ぶ必要があります。

たとえば、次のようなコードがあったとします。

import time

def my_function():
    time.sleep(1)  # 1秒間のウェイト処理
    for i in range(1000):
        pass

start_time = time.time()
for i in range(10):
    my_function()
end_time = time.time()

print(f"経過時間:{end_time - start_time}秒")

実行結果

経過時間:10.01563262939453秒

このコードでは、my_functionを10回呼び出しています。

しかし、my_function内の処理時間のほとんどは、sleep関数によるウェイト処理が占めています。

本当に計測したいのは、forループの処理時間だったはずです。

しかし、計測結果は、ウェイト処理の時間に大きく影響されてしまっています。

したがって、処理時間を計測する際は、計測対象を適切に選ぶ必要があります。

計測したい処理に集中し、不要な処理は計測対象から外しましょう。

●処理時間計測の活用例

処理時間の計測は、単に時間を測るだけではなく、プログラムの改善に役立てることが重要です。

ここでは、処理時間計測の具体的な活用例を3つ紹介します。

○ボトルネックの特定

プログラムの処理速度が遅い場合、まずは処理時間の計測を行い、ボトルネックを特定することが重要です。

ボトルネックとは、プログラム全体の処理速度を律速している部分のことを指します。

たとえば、次のようなコードがあったとします。

import time

def process_data(data):
    # データ処理
    time.sleep(0.1)

def load_data():
    # データ読み込み
    time.sleep(1)

def save_data(data):
    # データ保存
    time.sleep(0.5)

start_time = time.time()

data = load_data()
processed_data = process_data(data)
save_data(processed_data)

end_time = time.time()

print(f"経過時間:{end_time - start_time}秒")

実行結果

経過時間:1.6050968170166016秒

このコードでは、load_dataprocess_datasave_dataの3つの関数が順番に呼び出されています。

計測結果から、load_data関数の処理時間が最も長いことがわかります。

つまり、load_data関数がボトルネックになっていると考えられます。

このような場合は、load_data関数の処理を最適化することで、プログラム全体の処理速度を改善できる可能性があります。

○アルゴリズムの比較

処理時間の計測は、異なるアルゴリズムの性能比較にも役立ちます。

たとえば、ソートアルゴリズムを比較する場合、次のようなコードを書くことができます。

import random
import time

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + middle + quick_sort(right)

# ランダムな配列を生成
data = [random.randint(1, 100) for _ in range(10000)]

# バブルソートの計測
start_time = time.time()
bubble_sort(data.copy())
end_time = time.time()
print(f"バブルソートの経過時間:{end_time - start_time}秒")

# クイックソートの計測
start_time = time.time()
quick_sort(data.copy())
end_time = time.time()
print(f"クイックソートの経過時間:{end_time - start_time}秒")

実行結果

バブルソートの経過時間:7.10120487213135秒
クイックソートの経過時間:0.03284192085266113秒

このコードでは、バブルソートとクイックソートの処理時間を比較しています。

計測結果から、クイックソートの方が圧倒的に高速であることがわかります。

このように、処理時間の計測を行うことで、アルゴリズムの性能差を定量的に評価することができます。

より高速なアルゴリズムを選択することで、プログラムの処理速度を大幅に改善できる可能性があります。

○パフォーマンス改善の評価

処理時間の計測は、パフォーマンス改善の効果を評価するためにも役立ちます。

たとえば、次のようなコードがあったとします。

import time

def process_data(data):
    result = []
    for item in data:
        result.append(item * 2)
    return result

start_time = time.time()

data = list(range(1000000))
processed_data = process_data(data)

end_time = time.time()

print(f"経過時間:{end_time - start_time}秒")

実行結果

経過時間:0.11181592941284180秒

このコードでは、process_data関数で大量のデータを処理しています。

ここで、処理速度を改善するために、リスト内包表記を使って書き換えてみます。

import time

def process_data(data):
    return [item * 2 for item in data]

start_time = time.time()

data = list(range(1000000))
processed_data = process_data(data)

end_time = time.time()

print(f"経過時間:{end_time - start_time}秒")

実行結果

経過時間:0.059055089950561523秒

リスト内包表記を使うことで、処理時間がほぼ半分になりました。

このように、処理時間の計測を行うことで、パフォーマンス改善の効果を定量的に評価することができます。

まとめ

本記事では、Pythonでプログラムの処理時間を計測する方法を4つ紹介しました。

Pythonでの処理時間の計測は、プログラムの最適化やデバッグに欠かせない技術です。

本記事で紹介した方法を参考に、ぜひ自分のプログラムの処理時間を計測してみてください。

処理時間の計測は、一見地味な作業かもしれません。

しかし、その一手間が、プログラムの性能を大きく左右する可能性があります。

効率的なプログラムを書くためには、処理時間の計測を習慣づけ、常に改善を心がけることが大切だと私は考えています。

本記事が、みなさんのPythonプログラミングのスキルアップの一助となれば幸いです。