LoginSignup
2
0

More than 1 year has passed since last update.

速度計測に便利なjupyterのマジックコマンド%timeitみたいなやつをPythonで自前実装

Last updated at Posted at 2020-05-10

やること

jupyterの%timeitみたいなやつを実装します。
これの返り値を変数として使いたい。

ipynb
%timeit -r 100 -n 10 test()

-r, -n 以外にもオプションがありますが、今回はこの2つ以外実装しません。

%timeit -r 100 -n 10 test() って何やってんの?

  1. test()を100回繰り返し、その中で一番実行速度の早かった値を返す。
  2. 1を10回繰り返し、平均をとる。

つまり、最速の10個の平均値を出しています。
(ちなみに %%timeit -r 100 -n 10 とするとセル単位の処理速度を計測できます。)

timeit.png

標準ライブラリのtimeitじゃダメなの?

  1. test()を100回繰り返し、その中で一番実行速度の早かった値を返す。

↑ これをやるメソッドがなさそう?
ひとまず勉強も兼ねて自前で実装してみます。

ディレクトリ構成

.
├── utils
│   ├── speed_test.py
│   └── __init__.py
└── main.py

コード

モジュール

speed_test.py
import time


class Measurer(object):
    def mean_time(self, num=10):
        """
        num回実行した結果の平均を返すデコレータ。
        """
        def outer_wrapper(func):
            def wrapper(*args, **kwargs):
                fast_time_list = [func(*args, **kwargs) for _ in range(num)]
                mean = sum(fast_time_list) / len(fast_time_list)
                return mean
            return wrapper
        return outer_wrapper

    def fast_time(self, repeat=10):
        """
        repeat回実行した中で最小値(実行速度の一番早かった値)を返すデコレータ。
        """
        def outer_wrapper(func):
            def wrapper(*args, **kwargs):
                result_list = [func(*args, **kwargs) for _ in range(repeat)]
                # print(result_list)
                min_time = min(result_list)
                # print(result_list)
                # print(min_time)
                return min_time
            return wrapper
        return outer_wrapper

    def onece_time(self, func):
        """
        引数に渡された関数の実行速度を返すデコレータ。
        """
        def wrapper(*args, **kwargs):
            # print('test start')
            start_time = time.time()
            func(*args, **kwargs)
            finish_time = time.time()
            elapsed_time = finish_time - start_time
            # print('elapsed_time => {:.10f}sec'.format(elapsed_time))
            # print('test finish')
            return elapsed_time
        return wrapper

    def execute(self, func, *args, num, repeat):
        """
        1. repeat回実行した中で最も速度の早かった値を算出。
        2. 1をnum回繰り返し、num個の最速の値の平均値を返す。
        """
        @self.mean_time(num=num)
        @self.fast_time(repeat=repeat)
        @self.onece_time
        def _execute(fn, *args):
            return fn(*args)
        return _execute(func, *args)

実行ファイル

main.py
from utils import speed_test


# 計測用の関数を定義
def test(max_count):
    count = 0
    for _ in range(max_count):
        count += 1


def main():
    max_count = 100
    num = 10
    repeat = 100

    measurer = speed_test.Measurer()
    # 第2引数以降、可変長で複数の引数を渡せる
    result = measurer.execute(test, max_count, num=num, repeat=repeat)
    print('result -> {:.12f}'.format(result))


if __name__ == '__main__':
    main()

可視化するパターン

返り値が取れるので可視化して比較も簡単に。

py:main.py
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display

from utils import speed_test


def test1(str_list):
    """
    文字列リストの全ての要素を数値変換する①
    """
    int_list = [int(i) for i in str_list]


def test2(str_list):
    """
    文字列リストの全ての要素を数値変換する②
    """
    int_list = list(map(int, str_list))


def main():
    num = 10
    repeat = 100

    # 文字列リストを生成
    str_list = [str(i) for i in range(10000)]
    # ['0', '1', '2', ... '9997', '9998', '9999']

    measurer = speed_test.Measurer()
    # 第2引数以降、可変長で複数の引数を渡せる
    result1 = measurer.execute(test1, str_list, num=num, repeat=repeat)
    result2 = measurer.execute(test2, str_list, num=num, repeat=repeat)
    # print('result -> {:.12f}'.format(result))

    df = pd.DataFrame({
        'for': [result1],
        'map': [result2]
    })

    display(df)

    x = ['for', 'map']
    y = [result1, result2]
    plt.bar(x, y)
    plt.show


if __name__ == '__main__':
    main()
for         map
0.001499    0.00109

名称未設定.png

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0