pythonのclassってオーバーヘッドあるの？

# モチベーション
「pythonのclassのインスタンス生成はまあまあオーバーヘッドがあるよ」といった内容を目にしたので、実際どうなのか検証してみたくなった。
# 検証要件
* 通常の関数実行と、classを介した関数実行の処理時間の差を調べてみる。
* 実行内容は同じものとする。


~~実行内容は適当~~
負荷がステップ数に対してリニアになるように修正した

## 実行環境
    Python 3.8.6 (default, Sep 30 2020, 04:00:38) 
    [GCC 10.2.0] on linux
## テスト1 普通に関数を呼び出す
普通に`def func():`で定義した関数を実行する

## テスト2 class内の関数を直接呼び出してみる
~~これはそもそもできなかった。
python shellだとできたっぽかったので謎・・・・~~
コメントもらったので修正したら動きました　感謝！

## テスト3 インスタンスを使い捨てにする
classを定義してその中に実行する関数を定義しておく
テスト時には毎回インスタンスを生成してからそのインスタンスの関数を指定回関数を実行する

## テスト4 インスタンスを一回だけ生成したあと指定回関数を実行する
テスト時には一回だけインスタンスを生成してからそのインスタンスの関数を指定回関数を実行する

## 予想
テスト1 >= テスト2 > テスト4 > テスト3

# コード
[テストコードをgistにおいておきます](https://gist.github.com/nepia11/aea5c28edae3e028d3a148a49c63cba6 "class_overhead_test.py")

``` python
# クラスのオーバヘッドを調べたい
import time
import random
import numpy


def func(a):
    arr = [random.random() for i in range(a)]
    return arr


class TestClass:
    @staticmethod
    def func(a):
        arr = [random.random() for i in range(a)]
        return arr


def test1(a):
    for i in range(a):
        # print("step"+str(i)+"\r", end="")
        func(5000)


# できない
def test2(a):
    for i in range(a):
        # print("step"+str(i)+"\r", end="")
        TestClass.func(5000)


def test3(a):
    for i in range(a):
        # print("step"+str(i)+"\r", end="")
        inst = TestClass()
        inst.func(5000)


def test4(a):
    inst = TestClass()
    for i in range(a):
        # print("step"+str(i)+"\r", end="")
        inst.func(5000)


def timer(str, before_time, do_print: bool):
    after = time.time()
    delta = after - before_time
    if do_print:
        print(str, "time:", delta)
    return after, delta


def tests(test_pass: int):
    test_range = 10000
    start = time.time()
    do_print = False
    after, delta = timer("init", start, do_print)
    results = []

    for i in range(test_pass):
        after, delta = timer("## start pass"+str(i), after, do_print)

        test1(test_range)
        after, delta1 = timer("test1 pure function", after, do_print)
        test2(test_range)
        after, delta2 = timer("test1 pure function", after, do_print)

        test3(test_range)
        after, delta3 = timer("test3 class instance function", after, do_print)

        test4(test_range)
        after, delta4 = timer(
            "test4 recreate class instance function 2", after, do_print)

        times = [delta1, delta2-delta1, delta3-delta1, delta4-delta1]
        result = ['{:.3f}'.format(d * 1000)+" ms" for d in times]
        print("result:", result)
        results.append(times)

    np_results = numpy.array(results)
    avg = numpy.mean(np_results,axis=0)
    result_max = numpy.max(np_results,axis=0)
    result_min = numpy.min(np_results,axis=0)
    print("avg",['{:.3f}'.format(d * 1000)+" ms" for d in avg])
    print("max",['{:.3f}'.format(d * 1000)+" ms" for d in result_max])
    print("min",['{:.3f}'.format(d * 1000)+" ms" for d in result_min])


tests(10)

```
2020-12-09 10:36 テスト中のprintを除きました
# 結果
### ログ 
ログの読み方は 

```
result: [
  テスト1の実行時間　(グローバルに定義した関数実行), 
  テスト1とテスト2の差　(class内の関数を直接実行),
  テスト1とテスト3の差　(インスタンスを使い捨てにして関数実行), 
  テスト1とテスト4の差　(インスタンスを一回だけ作って関数実行)
]
```

``` bash
$ python classtest.py 
result: ['4006.579 ms', '68.577 ms', '-18.707 ms', '6.839 ms']
result: ['4002.367 ms', '82.651 ms', '42.831 ms', '-1.522 ms']
result: ['3977.559 ms', '4.014 ms', '26.188 ms', '30.365 ms']
result: ['3979.178 ms', '2.634 ms', '6.867 ms', '36.912 ms']
result: ['4039.222 ms', '19.722 ms', '-56.722 ms', '-60.901 ms']
result: ['3977.754 ms', '0.158 ms', '-1.119 ms', '4.331 ms']
result: ['4006.763 ms', '66.898 ms', '13.037 ms', '-27.076 ms']
result: ['3983.793 ms', '-1.015 ms', '1.238 ms', '6.599 ms']
result: ['3986.033 ms', '-1.977 ms', '0.844 ms', '3.524 ms']
result: ['3987.402 ms', '-0.455 ms', '12.658 ms', '60.481 ms']
avg ['3994.665 ms', '24.121 ms', '2.712 ms', '5.955 ms']
max ['4039.222 ms', '82.651 ms', '42.831 ms', '60.481 ms']
min ['3977.559 ms', '-1.977 ms', '-56.722 ms', '-60.901 ms']
$ python classtest.py 
result: ['4369.676 ms', '0.548 ms', '-18.869 ms', '13.947 ms']
result: ['4413.803 ms', '122.461 ms', '-76.252 ms', '-66.669 ms']
result: ['4333.918 ms', '57.169 ms', '85.911 ms', '43.591 ms']
result: ['4370.604 ms', '-2.187 ms', '27.085 ms', '-7.270 ms']
result: ['4362.176 ms', '6.294 ms', '-9.066 ms', '-37.678 ms']
result: ['4322.759 ms', '24.429 ms', '100.745 ms', '58.491 ms']
result: ['4443.867 ms', '-89.710 ms', '-86.513 ms', '-41.652 ms']
result: ['4350.542 ms', '2.151 ms', '0.151 ms', '2.440 ms']
result: ['4405.102 ms', '36.448 ms', '-53.097 ms', '-66.898 ms']
result: ['4341.269 ms', '-0.235 ms', '14.676 ms', '30.385 ms']
avg ['4371.372 ms', '15.737 ms', '-1.523 ms', '-7.131 ms']
max ['4443.867 ms', '122.461 ms', '100.745 ms', '58.491 ms']
min ['4322.759 ms', '-89.710 ms', '-86.513 ms', '-66.898 ms']
```
## 考察と言うか感想
複数回実行すると結果が結構変わる
関数の内容をもっとシンプルにしたほうがいいのかな？

修正ついでに平均値、最大値、最小値を取ってみた

* ~~素の関数実行でも最大、最小で**120ms**ほど誤差があるみたい~~ print分の誤差でした
* ~~その他の条件でも大体それくらいの誤差に収まっている感じがする~~
* 一番最初に実行したのが一番遅いかと思ったけどそうでもない(jit最適化みたいなのが効いてるわけでもない？)

~~単に関数をラップした(初期化で何もしない)**classのオーバーヘッドはほとんど無いかも**~~

# 全然わからん
気にしなくていいかも

*ぷよぐやみんぐよわいからようけんがだとうかわからないよ*