1. リテラルの参照と変数の参照

リテラルの参照の方が速い
(ただし、list を除く)

1.1. int

標準ライブラリ timeit のご紹介

timeit は、コマンドラインで次のように打つことで、 実行時間を計測してくれます。

python -m timeit "Pythonのコード"

timeit の使い方、出力される結果の意味などについて、 以下の記事でご紹介させていただきました。 このさき、ずっと使うツールになるので、ざっくりとで構わないので、 もしご存知でなければ簡単に目を通しておいてください。

一行で比較

# リテラルの参照
python -m timeit "1;1;1"

# 変数の参照
python -m timeit -s "a=1" "a;a;a"

$ # リテラルの参照
$ python -m timeit "1;1;1"
100000000 loops, best of 3: 0.00956 usec per loop
$ 
$ # 変数の参照
$ python -m timeit -s "a=1" "a;a;a"
10000000 loops, best of 3: 0.0435 usec per loop
$ 

複数行で比較

# リテラルの参照
python -m timeit "1" "1" "1"

# 変数の参照
python -m timeit -s "a=1" "a" "a" "a"

$ # リテラルの参照
$ python -m timeit "1" "1" "1"
100000000 loops, best of 3: 0.00951 usec per loop
$ 
$ # 変数の参照
$ python -m timeit -s "a=1" "a" "a" "a"
10000000 loops, best of 3: 0.0454 usec per loop
$ 

1.2. str

一行で比較

# リテラルの参照
python -m timeit "'Hello';'Hello';'Hello'"

# 変数の参照
python -m timeit -s "s='Hello'" "s;s;s"

$ # リテラルの参照
$ python -m timeit "'Hello';'Hello';'Hello'"
100000000 loops, best of 3: 0.00995 usec per loop
$ 
$ # 変数の参照
$ python -m timeit -s "s='Hello'" "s;s;s"
10000000 loops, best of 3: 0.0472 usec per loop
$ 

複数行で比較

# リテラルの参照
python -m timeit "'Hello'" "'Hello'" "'Hello'"

# 変数の参照
python -m timeit -s "s='Hello'" "s" "s" "s"

$ # リテラルの参照
$ python -m timeit "'Hello'" "'Hello'" "'Hello'"
100000000 loops, best of 3: 0.0103 usec per loop
$ 
$ # 変数の参照
$ python -m timeit -s "s='Hello'" "s" "s" "s"
10000000 loops, best of 3: 0.0436 usec per loop
$ 

1.3. tuple

一行で比較

# リテラルの参照
python -m timeit "(1,2,3);(1,2,3);(1,2,3)"

# 変数の参照
python -m timeit -s "a=(1,2,3)" "a;a;a"

$ # リテラルの参照
$ python -m timeit "(1,2,3);(1,2,3);(1,2,3)"
10000000 loops, best of 3: 0.0577 usec per loop
$ 
$ # 変数の参照
$ python -m timeit -s "a=(1,2,3)" "a;a;a"
10000000 loops, best of 3: 0.0428 usec per loop
$ 

複数行で比較

# リテラルの参照
python -m timeit "(1,2,3)" "(1,2,3)" "(1,2,3)"

python -m timeit -s "a=(1,2,3)" "a" "a" "a"

$ # リテラルの参照
$ python -m timeit "(1,2,3)" "(1,2,3)" "(1,2,3)"
10000000 loops, best of 3: 0.0219 usec per loop
$ python -m timeit -s "a=(1,2,3)" "a" "a" "a"
10000000 loops, best of 3: 0.0237 usec per loop
$

1.4. list

list だけ変数を参照した方が速い。

一行で比較

# リテラルの参照
python -m timeit "[1,2,3];[1,2,3];[1,2,3]"

# 変数の参照
python -m timeit -s "a=[1,2,3]" "a;a;a"

$ # リテラルの参照
$ python -m timeit "[1,2,3];[1,2,3];[1,2,3]"
10000000 loops, best of 3: 0.186 usec per loop

$ # 変数の参照
$ python -m timeit -s "a=[1,2,3]" "a;a;a"
10000000 loops, best of 3: 0.0222 usec per loop

複数行で比較

# リテラルの参照
python -m timeit "[1,2,3]" "[1,2,3]" "[1,2,3]"

# 変数の参照
python -m timeit -s "a=[1,2,3]" "a" "a" "a"

$ # リテラルの参照
$ python -m timeit "[1,2,3]" "[1,2,3]" "[1,2,3]"
10000000 loops, best of 3: 0.185 usec per loop

$ python -m timeit -s "a=[1,2,3]" "a" "a" "a"
10000000 loops, best of 3: 0.0228 usec per loop

◯ なんでリストだけ結果が違うの?

答え: リストの場合、リテラルを参照するとオブジェクトが生成されます。

LOAD_CONST は定数の読み込みです。 tuple の場合、LOAD_CONST だけで済んでいます。 一方で list の場合、BUILD_LIST という list を生成する処理が走ってしまっています。

import dis
dis.dis("[1, 2, 3]")
dis.dis("(1, 2, 3)")
>>> import dis
>>> dis.dis("[1, 2, 3]")
  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (2)
              4 LOAD_CONST               2 (3)
              6 BUILD_LIST               3
              8 RETURN_VALUE
>>> dis.dis("(1, 2, 3)")
  1           0 LOAD_CONST               0 ((1, 2, 3))
              2 RETURN_VALUE
>>> 

しれっと結論だけ述べて dis について何も説明していませんでした。 Python はコードを実行するときに、バイトコードという 中間言語に変換されて、それからコードが実行されています。 dis は、その中間コードを表示してくれる標準ライブラリです。

自分も全然理解していないので、 そんなものもあるんだなくらいに押さえておいていただければと思います。

まとめ

リテラルの参照は、オブジェクトの生成→参照という2段階を踏んで、 単純に変数の参照するよりも重くなるかなと思っていました。 しかし実際には int, str, tuple については、 変数よりもリテラルを参照した方が早いことがわかりました。

Last Updated: 8/8/2019, 8:53:30 PM