これまで、あるコードブロックの処理時間を計測する際は、その処理の前後の時刻をtime.time()で取得し、下記のようにその時間差を計算していました。
>>> t0 = time.time() # Start time
>>> 何らかの処理
>>> t1 = time.time() # End time
>>> dt = t1 - t0
ところが最近、処理時間の計測にはtime.perf_time()を使ったほうが良いという記事をいくつか見かけたことから、自分でも調べたのでメモとしてまとめます。
環境
- OS: Ubuntu16.04LTS (CPU: Intel Core i3)
- Python3.7.2
クロックを取得する関数(timeモジュール)
timeモジュール には、クロックを取得する関数がいくつか用意されています。ここでは、float型の値を返す下記5つの関数の特徴を整理しました。
time.time()
- エポックからの秒数を返す
>>> import time
>>> time.time()
1574598285.6382985
戻り値は通常減少することはないが、2回呼び出しの間にシステムクロックの時刻を巻き戻して設定した場合は、1回めの値よりも低い値が変えることがある。(公式リファレンスより)
全てのシステムが1秒より高い精度で時刻を提供するとは限らないので注意が必要(公式リファレンスより)
(参考-1)公式リファレンス:time.time()
(参考-2)【Python】現在時刻を取得する(timeモジュール)
time.monotonic()
- モノトニック時刻(単調増加時刻。決して後戻りすることがない時計)の値を返します。
>>> time.monotonic()
32331.390529154
このため、正しい利用方法は、呼び出した2点間の時間差を計測すること(公式リファレンスより)
time.perf_counter()
- パフォーマンスカウンターの値を返す。スリープ中の経過時間も含まれる。
>>> time.perf_counter()
32350.671784519
短期間の計測が行われるよう、可能な限り高い分解能をもつ(公式リファレンスより)
(参考)公式リファレンス:time.perf_counter()
time.process_time()
- 現在のプロセスのシステムおよびユーザーCPU時間の合計値を返す。プロセスごとに定義され、スリープ中の経過時間は含まれない。
>>> time.process_time()
0.056177035
呼び出した2点間の時間差を計測するのに適している(公式リファレンス)
(参考)公式リファレンス:time.process_time()
time.thread_time()
- 現在のスレッドのシステムおよびユーザのCPU時間の合計値を返す。スレッドごとの定義され、スリープ中の経過時間は含まれない。
>>> time.thread_time()
0.058984828
同一のスレッドで連続して呼び出された2点間の時間差を計測(公式リファレンスより)
(参考)公式リファレンス:time.thread_time()
分解能の比較
こちらのサイトではWindowsの場合について比較されていました。本記事では、Ubuntu環境の場合について調べてみました。
time.get_clock_info()で分解能を比較
time.get_clock_info()のresolution属性を確認すると、指定されたクロックの分解能を知ることができます。これを使って前章の5つの関数の分解能を確認してみました。
>>> import time
>>> time.get_clock_info("time").resolution
1e-09
>>> time.get_clock_info("monotonic").resolution
1e-09
>>> time.get_clock_info("perf_counter").resolution
1e-09
>>> time.get_clock_info("process_time").resolution
1e-09
>>> time.get_clock_info("thread_time").resolution
1e-09
結果はいずれの関数の分解能も1e-09となり、同じでした。ここはWindowsと異なっていますね。
実測で分解能を比較してみる
それぞれの関数の分解能を以下のコードで実測してみました。
N = 50000
def calc_time_res(func):
t = 0
for _ in range(N):
t0 = func()
while True:
t1 = func()
if t1 != t0:
break
t += (t1 - t0)
res = t / N
print(f"{func.__name__}:\t{res:.10f}")
calc_time_res(time.time)
calc_time_res(time.monotonic)
calc_time_res(time.perf_counter)
calc_time_res(time.process_time)
calc_time_res(time.thread_time)
筆者の環境(Ubuntu16.04LTS@Core i3)での結果を以下に示します。
time: 0.0000002577
monotonic: 0.0000001296
perf_counter: 0.0000001362
process_time: 0.0000007693
thread_time: 0.0000007561
time.monotonic(), time.perf_counter(), time.time()の順に分解能が高い結果となりました。
まとめ: やっぱりperf_time()が良いのかな?
あるコードブロックの処理時間を測るのに適したtimeモジュールの関数についてまとめました。
- これまでtime.time()を使ってきましたが、システムクロックの影響を受けるという観点で使わないほうが良さそう
- 分解能の観点ではtime.monotonic()とtime.perf_counter()が最も高い(むしろtime.monotonic()の方が良い・・・)
- 本件に関する多くの記事でも記載があるように、timeitモジュールのデフォルトタイマーとしてtime.perf_time()が使われている
処理時間の計測はperf_time()を使ったほうが良さそうです。