[Python]timeitで処理時間を計測してみた

# はじめに
- 今回はPython標準ライブラリのtimeitモジュールを使って、プログラムの処理時間を計測してみたいと思います。
- 動作環境
    - Python 3.11.0
    - macOS Ventura

# timeitとは
timeitはPython標準ライブラリのモジュールで、小さなコードの計測に便利です。

timeitモジュールは、コードの一部を指定した回数だけ実行することで、その処理速度を計測して、平均処理時間を計測できます。また、デフォルトでは計測中、一時的にガベージコレクションを無効にすることで、個々の計測結果がより比較しやすくなります。

timeitを使って以下の３つのやり方で処理時間を計測してみます。
- pyファイル内で計測
- コマンドラインで計測
- 対話モードで計測

※処理時間は実行するマシンの性能によって異なる場合があります。

# pyファイル内で計測
## timeit.timeit()
```python
import timeit

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
```
timeit.timeit()は測定対象となるコード、setupコードおよび timer関数でTimerインスタンスを作成し、そのtimeit()メソッドを`number回`実行します。（デフォルトは100万回）
複数行を計測したい場合は、複数行のコード文字列を渡すか、セミコロンで区切ります。

例えば、２つの変数の値の入れ替え時に、一般的な方法として、一時変数を使う方法と、アンパックを使う方法がありますが、どちらの方が速いか知りたい場合は、以下のように測定できます。
```python
import timeit

# 一時変数を使った変数の値の入れ替え
print(timeit.timeit("a, b = 1, 2; temp = a; a = b; b = temp"))

# アンパックを使った変数の値の入れ替え
print(timeit.timeit("a, b = 1, 2; a, b = b, a"))
```
それぞれの（100万回実行した）処理時間が以下のように`秒単位`で出力されます。（アンパックの方が速いですね）
```
0.022119374996691477
0.01498649999848567
```

また、以下のように合計処理時間を実行回数で割ることで`平均処理時間`を計測できます。
```python
import timeit

# 2の100000乗を10万回計算し、その平均処理時間を計測
print(timeit.timeit("2 ** 100000", number=100000) / 100000)
```
```
0.0002520138779199624
```
実行回数で割らないと1回あたりの処理時間ではなく、合計処理時間になるのでご注意ください。また、重い処理の場合は終了まで時間がかかるのでnumberを減らしたほうがいいでしょう。


setup引数には`測定前に実行する処理`を記述できます。
```python
import timeit

# 1から100までの乱数を1000万回生成し、その合計処理時間を計測
# 測定前に乱数生成のためのrandomをimport
print(timeit.timeit("random.randint(1, 100)", setup="import random", number=10000000))
```
```
2.021007332994486
```

globals引数にはコードを実行する`名前空間`を指定します。デフォルトではtimeit.timeit()に渡す測定対象となるコードは、プログラムにある他の変数や関数にアクセスできません。globals()を指定することで`グローバルな名前空間`でコードを実行できます。
```python
import timeit

# sという変数を設定
s = "hello"
# sを1回出力する時間を測定
print(timeit.timeit("print(s)", number=1, globals=globals()))
```
```
hello
1.9999999494757503e-05
```

## timeit.repeat()
```python
import timeit

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)
```
timeit.repeat()はtimeit()を`repeat回`実行できます。（デフォルトは5回）
結果は`リスト`で返ってきます。setupやglobalsなどの引数の使い方はtimeit.timeit()と同じです。
```python
import timeit

# 1から100までの乱数を1000万回生成する処理を3回実行し、それぞれの処理時間を計測
print(timeit.repeat("random.randint(1, 100)",
      setup="import random", repeat=3, number=10000000))
```
```
[2.0110499169968534, 1.9915823749979609, 1.9966460420037038]
```

# コマンドラインで計測
コマンドラインからプログラムとして呼び出す場合は、以下のコマンドを使います。（オプションの説明はhelpオプションか公式ドキュメントをご参照ください）
```
python3 -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]
```
1から100までの乱数を1000万回生成する処理を3回実行するコマンドを実行してみます。
```bash
python3 -m timeit -n 10000000 -r 3 -s "import random" "random.randint(1, 100)"
```
3回実行した中で`最も速かった平均値`（1から100までの乱数を生成する処理の1回あたりの処理時間）が出力されます。（200ナノ秒）
```
10000000 loops, best of 3: 200 nsec per loop
```

# 対話モードで計測
pyファイル内で計測するのとほぼ同じやり方です。スクリプトを書くまでもないちょっとした動作確認の際に使うといいでしょう。
ターミナルで対話モード(インタラクティブモード）でpythonを起動して、timeit.timeit()を使って、1から100までの乱数を1000万回生成し、その処理時間を計測してみます。
```
>>> import timeit
>>> timeit.timeit("random.randint(1, 100)", setup="import random", number=10000000)
2.044745207997039
>>>
```

# 参考
- [timeit --- 小さなコード断片の実行時間計測 | docs.python.org](https://docs.python.org/ja/3/library/timeit.html)
- [Measuring Performance and Big O Algorithm Analysis | Beyond the Basic Stuff with Python](https://inventwithpython.com/beyond/chapter13.html)