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

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

```py:ipynb
%timeit -r 100 -n 10 test()
```
`-r`, `-n` 以外にもオプションがありますが、今回はこの２つ以外実装しません。



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

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

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

![timeit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/e79657df-df8d-9562-1a0a-3d02e60af99d.png)


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

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

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


https://docs.python.org/ja/3/library/timeit.html

# ディレクトリ構成

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


# コード

## モジュール

```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)
```

## 実行ファイル

```py: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()
    # 第２引数以降、可変長で複数の引数を渡せる
    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()
    # 第２引数以降、可変長で複数の引数を渡せる
    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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287998/3fe424fb-076c-2bed-71a7-188dcb688506.png)