Pythonで特定の処理群の実行時間(処理時間)を計測する

Last modified: 2021-07-23

Pythonで特定の処理群を実行するのにかかる時間を計測するにはtimeitというモジュールを用いる。

使い方

準備

まず準備としてtimeitモジュールをインポートして使用可能にしておく。

import timeit

処理時間計測機能の呼び出し

  • 関数timeit.time()の最初の引数1にPythonのコードの文字列(クォートされた文字列か、文字列の入った変数)を渡す
  • 同関数のキーワード引数setupに同様の形で渡した文字列は、実行時間計測の前に変数などの準備を行うPythonコードを渡すのに使える
    • 準備処理にかかった時間は実行時間に含まないようにできる
  • 同関数のキーワード引数numberでそのコードの実行を何回繰り返すかを指定
    • 省略時に適用される既定値は100万回もあるので、同等の処理を複数の書き方で記述して処理時間を比較するだけなら省略して問題ないことも多い

この関数の戻り値として処理時間が得られる。

他の言語の処理時間計測用の関数やオブジェクトでは計測開始や計測停止のタイミングで関数を呼び出すが、この方法はそれとは大きく使い方が異なる。

リスト型とcollections.deque型のそれぞれに連番を入れておき、最初の値を取り出して除くのを全要素に行うのにかかる時間を比べてみる。

(リスト型)
>>> timeit.timeit("while len(lst) > 0: lst.pop(0)", setup="lst = []\nfor i in range(114514): lst.append(i)")
3.4290177009997933

(collections.deque型)
>>> timeit.timeit("while len(que) > 0: que.popleft()", setup="from collections import deque\nque = deque([])\nfor i in range(114514): que.append(i)")
0.12383000899990293

リストをキューとして使う(=最初に入れたデータを最初に取り出す)よりはcollections.deque型を使用したほうが圧倒的に速い2ということが分かる。

なお、変数の準備の記述が後ろに来ると全体の流れが見づらい場合にはキーワード引数stmtを後ろに持ってくると見やすくなる場合がある。

(stmtを後ろに配置した例)
>>> timeit.timeit(setup="lst = []\nfor i in range(114514): lst.append(i)", stmt="while len(lst) > 0: lst.pop(0)")
>>> timeit.timeit(setup="from collections import deque\nque = deque([])\nfor i in range(114514): que.append(i)", stmt="while len(que) > 0: que.popleft()")

クラスを用いる場合

クラスを用いる方法は処理時間計測の部分を何回も繰り返す場合などに便利。

  • timeit.Timer()クラスのオブジェクトを作成
    • stmtsetupといった引数は作成時の引数として渡す
  • そのオブジェクトのメンバ関数timeit()を呼び出すたびに処理時間の結果が得られる
    • number引数を指定する場合はここで指定する
(オブジェクト作成)
>>> t = timeit.Timer(setup="from collections import deque\nlst = deque([])\nfor i in range(114514): lst.append(i)", stmt="while len(lst) > 0: lst.popleft()")

(処理時間計測)
>>> t.timeit(number=10000000)
0.9805724760008161

(更に繰り返し実行してみる)
>>> t.timeit(number=10000000)
0.9808024780013511
>>> t.timeit(number=10000000)
0.9857495670003118

関数を定義してこれを実行する時間を計測する

スクリプトの中で処理時間を計測したい部分を関数にしておいて、timeit.timeit()でその関数の呼び出し処理をstmt引数に記述しておくと便利なことがある。

その際、関数の名前が実行時に未定義となってエラーにならないようにglobals=globals()指定を追加するとよい。

#! /usr/bin/python3

import timeit
from collections import deque


def prep_list(length):
    lst = []
    for i in range(length):
        lst.append(i)
    return lst


def prep_deque(length):
    que = deque([])
    for i in range(length):
        que.append(i)
    return que


def do_task_list(lst):
    while len(lst) > 0:
        lst.pop(0)


def do_task_deque(que):
    while len(que) > 0:
        que.popleft()


if __name__ == "__main__":
    data_len = 114514
    print(
        "list -> {}".format(
            timeit.timeit(
                setup="q = prep_list({})".format(data_len),
                stmt="do_task_list(q)",
                globals=globals())))
    print(
        "deque -> {}".format(
            timeit.timeit(
                setup="q = prep_deque({})".format(data_len),
                stmt="do_task_deque(q)",
                globals=globals())))

出力例:

list -> 3.4471844719992077
deque -> 0.23104157700072392

  1. キーワード引数で指定する場合の名前はstmt ↩︎

  2. Python公式チュートリアルによると、リスト型をキューとして使うと要素をずらす処理が発生して効率が悪いらしく、collections.deque型はキューとして効率よく扱えるように設計されているとのこと ↩︎