0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-08

モチベーション

「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においておきます

# クラスのオーバヘッドを調べたい
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の差 (インスタンスを一回だけ作って関数実行)
]
$ 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のオーバーヘッドはほとんど無いかも

全然わからん

気にしなくていいかも

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

0
0
3

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?