見出し画像

Pythonでシンプルに処理時間を測る方法

この記事はPythonプログラミングに関する超小ネタである。

Python(に限らず)システムを開発でパフォーマンス(動作性能)の問題が生じる場合がある。

パフォーマンス問題の解決においてよくやるのが、処理時間の計測である。line_profiler などのツールを使って計測する方法もあるが、そういった外部ツールを一切使わずにシンプルに計測したい場合もある。

そういう場合は、datetime や time ライブラリを用いてシステム時計にアクセスし、プログラム中の狙った箇所の経過時間を測るという方法がまず頭に浮かぶ。たぶん基本的には以下のようなコードになるだろう:

import datetime

ts0 = datetime.datetime.now().timestamp()
# 時間を測りたい処理 >>
for i in range(100000):
  pass
# 時間を測りたい処理 <<
ts1 = datetime.datetime.now().timestamp()

print(f"経過時間={ts1-ts0:.3f}秒")

処理時間を測りたい箇所が数カ所ならこれで十分であるが、もっといろんな箇所を測りたくなってくると、このスタイルでは煩雑である。もっと楽にできないだろうか?

そこで、この処理時間の計測という仕事を抽象化してみると、(1)任意の処理コードの塊があり、(2)その塊を開始する直前に時刻t0を計測して保持し、(3)その塊が終了した直後に時刻t1を計測して t1-t0 を出力する。となる。

気づく人はすぐに気づくと思うが、これは典型的な前処理後処理を必ずペアで実施しなければならないパターンである。このパターンに対応する便利な機構がPythonにはある。with 文コンテキストマネージャーである。

with文 は「ある処理の前・後に必ず実行しなければならない処理がある」という状況で便利な機構である。この前後の必須処理を含めた一連の処理ブロックをコンテキストといい、それを実装したものがコンテキストマネージャーである。そして、そのインスタンスを with 文に続けて書くと、自動的に前処理・本体処理・後処理を実行することを保証してくれる。

公式ドキュメントにもあるように、コンテキストマネージャーの規約は:

def __enter__(...):
def __exit__(...):

という2つのメソッドを持つオブジェクトであること、だけである。このメソッドを定義したクラスを書くこともできるが、このケースでは多少煩雑に感じる。

そういう場合は、contextlib を用いて以下のようにジェネレータの形式でコンテキストマネージャーを書くことができる:

from contextlib import contextmanager
import time

# コンテキストマネージャー 'time_log' を作る
@contextmanager
def time_log(msg:str)->None:
    """with構文で使うことにより、処理時間を print 出力するための関数
        例:
        with time_log(l"処理A"):
            proc_A()
            
        とすると、proc_A()の処理時間をprint出力する。
    """
    print(f"{msg}-開始")
    st_tm:float = time.time()
    yield
    ed_tm:float = time.time()
    print(f"{msg}-終了:所要時間 {ed_tm-st_tm:.3f} [秒]")


# time_log の仕様例
with time_log("ある処理"):
  # 時間を測りたい処理 >>
  for i in range(100000):
    pass
  # 時間を測りたい処理 <<

#
# 実行結果例
#
# ある処理-開始
# ある処理-終了:所要時間 0.006 [秒]

ここで定義したコンテキストマネージャー time_log は、上記コード中のコメントにも書いたように、処理時間を計測したいコードブロックを with time_log(..): 配下のブロックとするだけでOKなので、非常に気軽に処理計測を行うことができる。print文のところを logger 出力などに置き換えれば、本格的にシステムに組み込むこともできるだろう。ぜひ利用してみてほしい。

今回の記事は以上です。お読みいただきありがとうございました。



この記事が気に入ったらサポートをしてみませんか?