[Python] 各種変数へのアクセス速度比較


# 概要
Pythonで1億回の単位のループの中で変数へのアクセス速度を比較したところ、下記のような傾向が見られた。

* local変数はglobal変数より早い。
* クラス内からクラス変数にアクセスする際は、self.～でアクセスした方が早い。
* クラス内からselfでクラス変数にアクセスするのと、インスタンス変数にアクセスするのは同程度の速度。
* ただの数値と変数に格納した数値へのアクセスはほとんど変わらない。

これらの結果をまとめる。なお、Pythonのバージョンは3.9.12を用いた。
**環境依存で変わる可能性があるため、必要に応じて後述のコードで確認してください。**

# 結果
後述するプログラムで各処理に1億回アクセスした際の実行結果を先に示す。

~~~
数値 1.6297178 s
クラス外からクラス変数(staticに) 3.2673602000000006 s
クラス外からクラス変数(objectから) 2.5934605999999993 s
クラス内からクラス変数(staticに) 3.272198800000001 s
クラス内からクラス変数(selfで) 2.607861999999999 s
クラス内からインスタンス変数 2.8200494000000003 s
クラス内で変数 1.6768085 s
local変数 1.6274557000000023 s
global変数 2.3262558999999996 s
~~~~

このように、できるだけローカル変数を使うことが望ましいことが分かった。
また、staticな変数にアクセスする際は、生成したobjectからではなく、クラスからアクセスするのが一般的である。
しかし、生成したオブジェクトやselfでアクセスした方が速度的には有利であることが分かった。

実行したプログラムは以下に示す。

~~~
import time

g_value = 1 # グローバル変数
i_max = 100000000

def main():
    l_value = 1 # ローカル変数

    # 数値
    start_time = time.perf_counter()
    for _ in range(i_max):
        test = 1
    end_time = time.perf_counter()
    print("数値", end_time - start_time, "s")

    # クラス変数など
    my_test = Test()

    start_time = time.perf_counter()
    for _ in range(i_max):
        test = Test.c_value
    end_time = time.perf_counter()
    print("クラス外からクラス変数(staticに)", end_time - start_time, "s")

    start_time = time.perf_counter()
    for _ in range(i_max):
        test = my_test.c_value
    end_time = time.perf_counter()
    print("クラス外からクラス変数(objectから)", end_time - start_time, "s")

    my_test.class_static()
    my_test.class_self()
    my_test.instance()
    my_test.valuable()

    # ローカル変数
    start_time = time.perf_counter()
    for _ in range(i_max):
        test = l_value
    end_time = time.perf_counter()
    print("local変数", end_time - start_time, "s")
    
    # グローバル変数
    start_time = time.perf_counter()
    for _ in range(i_max):
        test = g_value
    end_time = time.perf_counter()
    print("global変数", end_time - start_time, "s")

class Test:
    c_value = 1

    def class_static(self):
        start_time = time.perf_counter()
        for _ in range(i_max):
            test = Test.c_value
        end_time = time.perf_counter()
        print("クラス内からクラス変数(staticに)", end_time - start_time, "s")

    def class_self(self):
        start_time = time.perf_counter()
        for _ in range(i_max):
            test = self.c_value
        end_time = time.perf_counter()
        print("クラス内からクラス変数(selfで)", end_time - start_time, "s")

    def instance(self):
        self.i_value = 1
        start_time = time.perf_counter()
        for _ in range(i_max):
            test = self.i_value
        end_time = time.perf_counter()
        print("クラス内からインスタンス変数", end_time - start_time, "s")

    def valuable(self):
        value = 1
        start_time = time.perf_counter()
        for _ in range(i_max):
            test = value
        end_time = time.perf_counter()
        print("クラス内で変数", end_time - start_time, "s")

main()
~~~

# 考察
1億回で1秒程度の差であるため、可読性を優先するべきではあるが、最大で倍程度の違いが出ることは気に留めておいても良いかもしれない。
また、誤動作を引き起こしやすいグローバル変数は、速度の面でも劣っていることが明らかで、極力使用を控えるべきだろう。

# 筆者が実施した他の速度比較
[[Python] list, tuple, dict, bitへのアクセス速度比較](https://qiita.com/KYUPHD/items/e9232713e4470d7a11a6)