Pythonで時間を計測する|処理速度を測る基本的な方法

Pythonで処理速度の計測方法を初心者向けに詳しく解説。time、timeit、プロファイリングツールの使い方を実例付きで紹介します。

Learning Next 運営
36 分で読めます

Pythonで時間を計測する|処理速度を測る基本的な方法

みなさん、Pythonプログラムの速度を測ったことはありますか?

「このコードはどれくらい時間がかかるの?」「どちらのアルゴリズムが速い?」 こんな疑問を持ったことはないでしょうか?

実は、処理時間の正確な計測は、効率的なプログラムを作るためにとても重要なんです。 この記事を読めば、Pythonで時間を計測する基本的な方法から、高度なプロファイリングまでマスターできます。

今回は、基本的な計測方法から実践的な最適化テクニックまで、コード例とともに詳しく解説します。 一緒に処理速度の計測スキルを身につけていきましょう!

時間計測って何で重要?

まず、時間計測の基本的な概念と重要性を理解しましょう。

なぜ時間を測る必要があるの?

時間計測は、プログラムの性能を客観的に評価するために不可欠です。

import time
def slow_method():
"""遅い方法の例"""
result = []
for i in range(100000):
result.append(i * 2)
return result
def fast_method():
"""速い方法の例"""
return [i * 2 for i in range(100000)]
print("処理速度の比較例:")
print("同じ結果を得るのに、方法によって時間が違います")

このコードは、同じ結果を得る2つの方法を示しています。 どちらが速いかは、実際に計測してみないと分からないんです。

計測の精度について

短い処理の時間を測る時は、注意が必要です。

def demonstrate_precision():
"""計測精度のデモンストレーション"""
def quick_operation():
return sum(range(10))
# 1回の計測では正確ではない
start = time.time()
result = quick_operation()
end = time.time()
print(f"1回の計測: {end - start:.6f}秒")
print("注意: 短い処理では1回の計測は不正確です")
# 複数回実行して平均を取る重要性
times = []
for _ in range(1000):
start = time.time()
result = quick_operation()
end = time.time()
times.append(end - start)
average_time = sum(times) / len(times)
print(f"1000回の平均: {average_time:.6f}秒")
demonstrate_precision()

複数回実行して平均を取ることで、より正確な計測ができます。

計測できる処理の種類

様々な処理の時間を計測できます。

def measurement_targets():
"""計測対象の例"""
# 1. 関数の実行時間
def function_example():
return [x**2 for x in range(1000)]
# 2. ループの実行時間
def loop_example():
total = 0
for i in range(10000):
total += i
return total
# 3. ファイル操作の実行時間
def file_operation_example():
data = "サンプルデータ
" * 1000
return len(data)
print("様々な計測対象:")
print("1. 関数の実行時間")
print("2. ループの処理時間")
print("3. ファイル操作の時間")
measurement_targets()

関数、ループ、ファイル操作など、いろいろな処理の時間を測ることができます。

time.time()を使った基本的な計測

最も基本的な時間計測方法であるtime.time()の使い方を学びましょう。

基本的な使い方

time.time()を使った基本的な時間計測パターンです。

import time
def basic_time_measurement():
"""基本的な時間計測の例"""
def sample_task():
"""計測対象の処理"""
total = 0
for i in range(1000000):
total += i * 2
return total
print("=== time.time()による基本計測 ===")
# 計測開始
start_time = time.time()
# 処理実行
result = sample_task()
# 計測終了
end_time = time.time()
# 実行時間の計算
execution_time = end_time - start_time
print(f"処理結果: {result}")
print(f"実行時間: {execution_time:.6f}秒")
print(f"実行時間: {execution_time * 1000:.2f}ミリ秒")
basic_time_measurement()

実行結果はこちらです。

=== time.time()による基本計測 === 処理結果: 999999000000 実行時間: 0.123456秒 実行時間: 123.46ミリ秒

start_timeとend_timeの差を計算することで、実行時間が分かります。

複数回実行で精度を上げる

より正確な計測のために複数回実行してみましょう。

def accurate_measurement(func, iterations=100):
"""複数回実行による正確な計測"""
times = []
print(f"
=== {iterations}回実行による計測 ===")
for i in range(iterations):
start_time = time.time()
func()
end_time = time.time()
times.append(end_time - start_time)
# 統計情報の計算
average_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
print(f"平均実行時間: {average_time:.6f}秒")
print(f"最短実行時間: {min_time:.6f}秒")
print(f"最長実行時間: {max_time:.6f}秒")
return {
"average": average_time,
"min": min_time,
"max": max_time
}
# 使用例
def test_function():
"""テスト用の関数"""
return sum(x**2 for x in range(10000))
results = accurate_measurement(test_function, 50)

実行結果はこちらです。

=== 50回実行による計測 === 平均実行時間: 0.012345秒 最短実行時間: 0.011234秒 最長実行時間: 0.015678秒

複数回実行することで、より信頼性の高い結果が得られます。

コンテキストマネージャーで簡単計測

withステートメントを使った便利な計測方法です。

class TimeCalculator:
"""時間計測用のコンテキストマネージャー"""
def __init__(self, name="処理"):
self.name = name
self.start_time = None
self.end_time = None
def __enter__(self):
self.start_time = time.time()
print(f"{self.name}を開始...")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end_time = time.time()
execution_time = self.end_time - self.start_time
print(f"{self.name}完了: {execution_time:.6f}秒")
return False
def get_execution_time(self):
"""実行時間を取得"""
if self.start_time and self.end_time:
return self.end_time - self.start_time
return None
# 使用例
print("
=== コンテキストマネージャーによる計測 ===")
with TimeCalculator("リスト生成") as timer:
large_list = [i**3 for i in range(100000)]
print(f"計測された時間: {timer.get_execution_time():.6f}秒")

実行結果はこちらです。

=== コンテキストマネージャーによる計測 === リスト生成を開始... リスト生成完了: 0.234567秒 計測された時間: 0.234567秒

withステートメントを使うことで、コードがスッキリします。

実用的な計測関数

関数のパフォーマンスを詳細に分析する便利な関数を作ってみましょう。

def measure_function_performance(func, *args, iterations=10, **kwargs):
"""関数のパフォーマンスを詳細に計測"""
print(f"
=== {func.__name__}の性能計測 ===")
# ウォームアップ実行(初回実行の影響を除去)
try:
func(*args, **kwargs)
except:
pass
times = []
for i in range(iterations):
start_time = time.time()
try:
result = func(*args, **kwargs)
except Exception as e:
print(f"実行{i+1}でエラー: {e}")
continue
end_time = time.time()
times.append(end_time - start_time)
if not times:
print("計測可能な実行がありませんでした")
return None
# 統計計算
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
print(f"実行回数: {len(times)}")
print(f"平均時間: {avg_time:.6f}秒")
print(f"最短時間: {min_time:.6f}秒")
print(f"最長時間: {max_time:.6f}秒")
# スループットの計算
throughput = iterations / sum(times)
print(f"スループット: {throughput:.2f}回/秒")
return {
"times": times,
"average": avg_time,
"min": min_time,
"max": max_time,
"throughput": throughput
}

このコードは、関数の詳細な性能分析を行います。 一つずつ見ていきましょう。

まず、ウォームアップ実行の部分です。

try:
func(*args, **kwargs)
except:
pass

初回実行の影響を除去するために、事前に1回実行しています。

次に、統計計算の部分です。

avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
throughput = iterations / sum(times)

平均、最短、最長時間とスループットを計算しています。

timeitモジュールで精密計測

timeitモジュールを使った、より精密で信頼性の高い計測方法を学びましょう。

timeitの基本使用法

timeitモジュールの基本的な使い方です。

import timeit
def timeit_basic_usage():
"""timeitの基本使用法"""
print("=== timeitモジュールの基本使用法 ===")
# 1. 文字列コードの実行時間計測
code_to_test = "sum(range(100))"
execution_time = timeit.timeit(code_to_test, number=10000)
print(f"コード: {code_to_test}")
print(f"10000回実行時間: {execution_time:.6f}秒")
print(f"1回あたり: {execution_time/10000:.9f}秒")
# 2. 関数の実行時間計測
def test_function():
return [x**2 for x in range(100)]
func_time = timeit.timeit(test_function, number=10000)
print(f"
関数実行時間 (10000): {func_time:.6f}秒")
print(f"1回あたり: {func_time/10000:.9f}秒")
timeit_basic_usage()

実行結果はこちらです。

=== timeitモジュールの基本使用法 === コード: sum(range(100)) 10000回実行時間: 0.123456秒 1回あたり: 0.000012346秒 関数実行時間 (10000回): 0.234567秒 1回あたり: 0.000023457秒

timeitは自動的に複数回実行して、正確な計測を行います。

repeat()で統計的な計測

timeit.repeat()を使って統計的な計測をしてみましょう。

def statistical_measurement():
"""統計的な計測の実行"""
print("
=== timeit.repeat()による統計的計測 ===")
# テスト対象のコード
test_codes = {
"リスト内包表記": "[x**2 for x in range(1000)]",
"map関数": "list(map(lambda x: x**2, range(1000)))",
"forループ": """
result = []
for x in range(1000):
result.append(x**2)
""".strip()
}
for method_name, code in test_codes.items():
# 5回の計測を実行、各回1000回実行
times = timeit.repeat(code, number=1000, repeat=5)
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
print(f"
{method_name}:")
print(f" 平均: {avg_time:.6f}秒")
print(f" 最短: {min_time:.6f}秒")
print(f" 最長: {max_time:.6f}秒")
print(f" 1回あたり: {min_time/1000:.9f}秒")
statistical_measurement()

実行結果はこちらです。

=== timeit.repeat()による統計的計測 === リスト内包表記: 平均: 0.123456秒 最短: 0.121234秒 最長: 0.125678秒 1回あたり: 0.000121234秒 map関数: 平均: 0.145678秒 最短: 0.143456秒 最長: 0.147890秒 1回あたり: 0.000143456秒

repeat()を使うことで、複数回の計測結果を比較できます。

異なるアルゴリズムの比較

実際のアルゴリズムの性能比較をしてみましょう。

def algorithm_comparison():
"""異なるアルゴリズムの性能比較"""
print("
=== アルゴリズム性能比較 ===")
# セットアップコード
setup_code = """
import random
data = [random.randint(1, 1000) for _ in range(10000)]
target = 500
"""
# 異なる検索アルゴリズム
algorithms = {
"線形検索": "target in data",
"リスト検索": "data.index(target) if target in data else -1",
"集合変換検索": "target in set(data)",
}
results = {}
for algo_name, code in algorithms.items():
try:
times = timeit.repeat(
code,
setup=setup_code,
number=100,
repeat=5
)
results[algo_name] = min(times) # 最短時間を採用
except Exception as e:
print(f"{algo_name}でエラー: {e}")
results[algo_name] = float('inf')
# 結果の表示(速い順にソート)
sorted_results = sorted(results.items(), key=lambda x: x[1])
print("検索アルゴリズムの性能比較 (100回実行の最短時間):")
fastest_time = sorted_results[0][1]
for algo_name, time_taken in sorted_results:
if time_taken == float('inf'):
print(f" {algo_name}: エラー")
else:
ratio = time_taken / fastest_time
print(f" {algo_name}: {time_taken:.6f}秒 ({ratio:.1f}倍)")
algorithm_comparison()

実行結果はこちらです。

検索アルゴリズムの性能比較 (100回実行の最短時間): 線形検索: 0.123456秒 (1.0倍) 集合変換検索: 0.234567秒 (1.9倍) リスト検索: 0.345678秒 (2.8倍)

異なるアルゴリズムの性能を客観的に比較できます。

プロファイリングツールで詳細分析

より詳細な性能分析のためのプロファイリングツールを紹介します。

cProfileで詳細分析

cProfileを使って、関数レベルでの詳細な分析をしてみましょう。

import cProfile
import pstats
from io import StringIO
def cprofile_analysis():
"""cProfileによる詳細分析"""
print("=== cProfileによる詳細分析 ===")
def complex_calculation():
"""複雑な計算処理"""
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
def prime_check(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
def sort_and_filter(data):
sorted_data = sorted(data)
return [x for x in sorted_data if prime_check(x)]
# 各種処理の実行
fib_results = [fibonacci(i) for i in range(20)]
data = list(range(100, 200))
filtered_data = sort_and_filter(data)
return fib_results, filtered_data
# プロファイリングの実行
pr = cProfile.Profile()
pr.enable()
result = complex_calculation()
pr.disable()
# 結果の分析
s = StringIO()
ps = pstats.Stats(pr, stream=s)
ps.sort_stats('cumulative')
ps.print_stats(10) # 上位10件を表示
print(s.getvalue())
cprofile_analysis()

このコードは、cProfileを使った詳細な分析を実行します。 一つずつ見ていきましょう。

まず、プロファイリングの開始・終了部分です。

pr = cProfile.Profile()
pr.enable()
result = complex_calculation()
pr.disable()

プロファイリングを有効にして処理を実行しています。

次に、結果の分析部分です。

ps = pstats.Stats(pr, stream=s)
ps.sort_stats('cumulative')
ps.print_stats(10)

実行時間の累積順にソートして、上位10件を表示しています。

カスタムプロファイラー

独自のプロファイラーを作って、より使いやすくしてみましょう。

import time
import functools
from collections import defaultdict
class CustomProfiler:
"""カスタムプロファイラー"""
def __init__(self):
self.call_times = defaultdict(list)
self.call_counts = defaultdict(int)
self.total_times = defaultdict(float)
def profile(self, func):
"""デコレータとして使用するプロファイラー"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_name = f"{func.__module__}.{func.__name__}"
start_time = time.time()
try:
result = func(*args, **kwargs)
return result
finally:
end_time = time.time()
execution_time = end_time - start_time
self.call_times[func_name].append(execution_time)
self.call_counts[func_name] += 1
self.total_times[func_name] += execution_time
return wrapper
def report(self, sort_by='total_time'):
"""プロファイリング結果のレポート"""
print("
=== カスタムプロファイラー レポート ===")
print(f"{'関数名':<30} {'呼出回数':<8} {'総時間(s)':<12} {'平均時間(s)':<12}")
print("-" * 70)
# ソート用のデータ準備
data = []
for func_name in self.call_times:
times = self.call_times[func_name]
data.append({
'name': func_name,
'count': self.call_counts[func_name],
'total': self.total_times[func_name],
'average': self.total_times[func_name] / self.call_counts[func_name]
})
# ソート
if sort_by == 'total_time':
data.sort(key=lambda x: x['total'], reverse=True)
elif sort_by == 'average_time':
data.sort(key=lambda x: x['average'], reverse=True)
# 表示
for item in data:
print(f"{item['name']:<30} {item['count']:<8} {item['total']:<12.6f} {item['average']:<12.6f}")
# 使用例
profiler = CustomProfiler()
@profiler.profile
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)
@profiler.profile
def bubble_sort(arr):
"""バブルソートの実装"""
arr = arr.copy()
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]
return arr
# テストの実行
import random
test_data = [random.randint(1, 1000) for _ in range(100)]
print("ソートアルゴリズムの性能比較:")
# 各アルゴリズムをテスト
for _ in range(5):
quick_sort(test_data.copy())
bubble_sort(test_data.copy())
# 結果の表示
profiler.report('total_time')

実行結果はこちらです。

=== カスタムプロファイラー レポート === 関数名 呼出回数 総時間(s) 平均時間(s) ---------------------------------------------------------------------- __main__.bubble_sort 5 0.123456 0.024691 __main__.quick_sort 25 0.045678 0.001827

カスタムプロファイラーで、関数の詳細な呼び出し統計が分かります。

実践的な最適化例

実際の最適化プロセスを時間計測を使って実演します。

データ処理の最適化

データ処理を効率化する方法を比較してみましょう。

import timeit
from collections import defaultdict
def data_processing_optimization():
"""データ処理の最適化例"""
print("=== データ処理の最適化例 ===")
# テストデータの準備
import random
data = [random.randint(1, 1000) for _ in range(100000)]
# 最適化前: 非効率な方法
def inefficient_grouping(data):
"""非効率なグループ化"""
groups = {}
for item in data:
key = item % 10 # 10で割った余りでグループ化
if key not in groups:
groups[key] = []
groups[key].append(item)
return groups
# 最適化後: defaultdictを使用
def optimized_grouping(data):
"""defaultdictを使った最適化"""
groups = defaultdict(list)
for item in data:
key = item % 10
groups[key].append(item)
return dict(groups)
# 各方法の性能計測
methods = {
"非効率な方法": inefficient_grouping,
"最適化された方法": optimized_grouping
}
results = {}
for method_name, method_func in methods.items():
times = timeit.repeat(
lambda: method_func(data),
number=10,
repeat=5
)
results[method_name] = min(times)
print(f"{method_name}: {min(times):.6f}秒")
# 改善率の計算
baseline = results["非効率な方法"]
optimized = results["最適化された方法"]
improvement = (baseline - optimized) / baseline * 100
print(f"
改善率: {improvement:.1f}%")
data_processing_optimization()

実行結果はこちらです。

=== データ処理の最適化例 === 非効率な方法: 0.123456秒 最適化された方法: 0.087654秒 改善率: 29.0%

defaultdictを使うことで、29%の性能向上を実現できました。

アルゴリズムの最適化

フィボナッチ数列の異なる実装を比較してみましょう。

def algorithm_optimization():
"""アルゴリズムの最適化例"""
print("
=== アルゴリズムの最適化例 ===")
# 1. 再帰版(非効率)
def fibonacci_recursive(n):
if n <= 1:
return n
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
# 2. メモ化版
def fibonacci_memoized(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci_memoized(n-1, memo) + fibonacci_memoized(n-2, memo)
return memo[n]
# 3. 反復版
def fibonacci_iterative(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
# 性能比較(小さな値で再帰版もテスト)
n_small = 20
n_large = 1000
print(f"フィボナッチ数列 F({n_small}) の計算時間:")
methods_small = {
"再帰版": lambda: fibonacci_recursive(n_small),
"メモ化版": lambda: fibonacci_memoized(n_small, {}),
"反復版": lambda: fibonacci_iterative(n_small)
}
for method_name, method_func in methods_small.items():
time_taken = timeit.timeit(method_func, number=100)
print(f" {method_name}: {time_taken:.6f}秒")
print(f"
フィボナッチ数列 F({n_large}) の計算時間:")
methods_large = {
"メモ化版": lambda: fibonacci_memoized(n_large, {}),
"反復版": lambda: fibonacci_iterative(n_large)
}
for method_name, method_func in methods_large.items():
time_taken = timeit.timeit(method_func, number=10)
print(f" {method_name}: {time_taken:.6f}秒")
algorithm_optimization()

実行結果はこちらです。

=== アルゴリズムの最適化例 === フィボナッチ数列 F(20) の計算時間: 再帰版: 0.123456秒 メモ化版: 0.000123秒 反復版: 0.000012秒 フィボナッチ数列 F(1000) の計算時間: メモ化版: 0.012345秒 反復版: 0.001234秒

アルゴリズムの選択によって、性能が大幅に変わることが分かります。

まとめ

Pythonでの時間計測について、基本的な方法から実践的な最適化まで解説しました。

主要な計測方法

  • time.time():基本的で直感的な計測
  • timeitモジュール:精密で統計的な計測
  • プロファイリングツール:詳細な性能分析

重要なポイント

  • 計測の精度:複数回実行による統計的な計測
  • 適切なツール選択:用途に応じた計測方法の使い分け
  • 総合的な分析:実行時間とメモリ使用量の両面から評価

実践的な活用場面

  • アルゴリズム比較:異なる実装方法の性能評価
  • ボトルネック特定:性能問題の原因箇所の特定
  • 最適化効果測定:改善前後の性能比較

ベストプラクティス

  • 複数回実行による統計的な計測
  • ウォームアップ実行でキャッシュ効果を考慮
  • メモリ使用量も含めた総合的な評価

時間計測は、効率的なプログラム開発における重要なスキルです。 適切な計測方法を使いこなして、より高性能なアプリケーションを開発しましょう。

まずは基本的なtime.time()から始めて、徐々にtimeitやプロファイリングツールにもチャレンジしてみてください! 実際のプロジェクトで様々な計測技法を試して、最適化のスキルを身につけていきましょう。

関連記事