結論は表題の通りで、公式のものを入れたら速くなりました。変数アクセスで数割程度。
理由は分かっていません。(全然関係ない話では、以前dockerではspectre対策で処理が遅くなる、みたいな話もあったので、そのようなものも含むビルド時の何らかの最適化とかかなと思うのですが、不明です...)
以下、ストーリーをお送りします。
ストーリー
ある晴れた日、"Literal"という記述がPython3.7でエラーを起こしたのを目にした私は、Python3.8で追加された機能のPEPを見ていました。
Literal types
https://www.python.org/dev/peps/pep-0586/
TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys
https://www.python.org/dev/peps/pep-0589/
Adding a final qualifier to typing
https://www.python.org/dev/peps/pep-0591/
へー、と思って調子に乗った私は、ついでに3.9で追加された機能もチェックしていました。
そうすると、面白い記述を見つけました。
https://docs.python.org/ja/3.9/whatsnew/3.9.html#optimizations
3.4〜3.9に至るまで、変数へのアクセススピードがどのように改善されてきたか、というデータがあったのです。
|Python version | 3.4|3.5|3.6|3.7|3.8|3.9|
|:--|--:|--:|--:|--:|--:|--:|--:|
|Variable and attribute read access:|||||||
| read_local | 7.1|7.1|5.4|5.1|3.9|4.0|
| read_nonlocal | 7.1|8.1|5.8|5.4|4.4|4.8|
| read_global |15.5|19.0|14.3|13.6|7.6|7.7|
| read_builtin |21.1|21.6|18.5|19.0|7.5|7.7|
| read_classvar_from_class |25.6|26.5|20.7|19.5|18.4|18.6|
| read_classvar_from_instance |22.8|23.5|18.8|17.1|16.4|20.1|
| read_instancevar |32.4|33.1|28.0|26.3|25.4|27.7|
| read_instancevar_slots |27.8|31.3|20.8|20.8|20.2|24.5|
| read_namedtuple |73.8|57.5|45.0|46.8|18.4|23.2|
| read_boundmethod |37.6|37.9|29.6|26.9|27.7|45.9|
|Variable and attribute write access:|||||||
| write_local | 8.7|9.3|5.5|5.3|4.3|4.2|
| write_nonlocal |10.5|11.1|5.6|5.5|4.7|4.9|
| write_global |19.7|21.2|18.0|18.0|15.8|17.2|
| write_classvar |92.9|96.0|104.6|102.1|39.2|43.2|
| write_instancevar |44.6|45.8|40.0|38.9|35.5|40.7|
| write_instancevar_slots |35.6|36.1|27.3|26.6|25.7|27.7|
|Data structure read access:|||||||
| read_list |24.2|24.5|20.8|20.8|19.0|21.1|
| read_deque |24.7|25.5|20.2|20.6|19.8|21.6|
| read_dict |24.3|25.7|22.3|23.0|21.0|22.5|
| read_strdict |22.6|24.3|19.5|21.2|18.9|21.6|
|Data structure write access:|||||||
| write_list |27.1|28.5|22.5|21.6|20.0|21.6|
| write_deque |28.7|30.1|22.7|21.8|23.5|23.2|
| write_dict |31.4|33.3|29.3|29.2|24.7|27.8|
| write_strdict |28.4|29.9|27.5|25.2|23.1|29.8|
|Stack (or queue) operations:|||||||
| list_append_pop |93.4|112.7|75.4|74.2|50.8|53.9|
| deque_append_pop |43.5|57.0|49.4|49.2|42.5|45.5|
| deque_append_popleft |43.7|57.3|49.7|49.7|42.8|45.5|
|Timing loop:|||||||
| loop_overhead | 0.5|0.6|0.4|0.3|0.3|0.3|
These results were generated from the variable access benchmark script at: Tools/scripts/var_access_benchmark.py. The benchmark script displays timings in nanoseconds. The benchmarks were measured on an Intel® Core™ i7-4960HQ processor running the macOS 64-bit builds found at python.org.
ということでしたが、実際に3.8.3でベンチマークを取ることが流行ったので、私も手元の環境でベンチマークを取ってみることにしました。
ベンチマークソース:
https://github.com/python/cpython/blob/master/Tools/scripts/var_access_benchmark.py
(コピペで動きます)
環境
3.7.3
(多分brewで入れた)3.7.3で測定した結果は次の通りでした。
% nice -n 10 python3 var_access_benchmark.py
Variable and attribute read access:
4.3 ns read_local
4.9 ns read_nonlocal
13.7 ns read_global
21.8 ns read_builtin
17.6 ns read_classvar_from_class
16.3 ns read_classvar_from_instance
25.5 ns read_instancevar
22.9 ns read_instancevar_slots
45.7 ns read_namedtuple
26.0 ns read_boundmethod
Variable and attribute write access:
4.8 ns write_local
5.1 ns write_nonlocal
18.7 ns write_global
81.8 ns write_classvar
36.8 ns write_instancevar
26.8 ns write_instancevar_slots
Data structure read access:
19.7 ns read_list
19.4 ns read_deque
21.3 ns read_dict
19.3 ns read_strdict
Data structure write access:
20.0 ns write_list
21.7 ns write_deque
26.0 ns write_dict
24.5 ns write_strdict
Stack (or queue) operations:
70.3 ns list_append_pop
49.8 ns deque_append_pop
49.0 ns deque_append_popleft
Timing loop overhead:
0.3 ns loop_overhead
2017のi5ですが、3.7のものと比較すると単一スレッドではi7より多少速い場合もありますね。
3.8.3(brew install python@3.8)
% nice -n 10 /usr/local/opt/python@3.8/bin/python3.8 var_access_benchmark.py
Variable and attribute read access:
5.8 ns read_local
5.8 ns read_nonlocal
7.8 ns read_global
7.7 ns read_builtin
19.8 ns read_classvar_from_class
20.1 ns read_classvar_from_instance
27.4 ns read_instancevar
25.9 ns read_instancevar_slots
21.2 ns read_namedtuple
31.6 ns read_boundmethod
Variable and attribute write access:
5.9 ns write_local
7.5 ns write_nonlocal
19.7 ns write_global
47.0 ns write_classvar
42.4 ns write_instancevar
31.3 ns write_instancevar_slots
Data structure read access:
23.4 ns read_list
24.8 ns read_deque
25.7 ns read_dict
22.8 ns read_strdict
Data structure write access:
24.9 ns write_list
29.1 ns write_deque
29.9 ns write_dict
27.8 ns write_strdict
Stack (or queue) operations:
62.0 ns list_append_pop
51.2 ns deque_append_pop
52.4 ns deque_append_popleft
Timing loop overhead:
0.4 ns loop_overhead
ファッ!?
3.8.3の方がだいたい速くなるはずなのに...
でも、read_namedtupleの傾向などは、3.8の傾向を示してはいます。
(read_namedtupleは、バージョンアップで劇的に性能が改善しています)
3.8.3(公式)
公式から3.8.3を入れて、python3で3.8.3が呼ばれるようにしました。
% nice -n 10 python3 var_access_benchmark.py
Variable and attribute read access:
3.9 ns read_local
4.4 ns read_nonlocal
7.2 ns read_global
7.1 ns read_builtin
17.1 ns read_classvar_from_class
16.2 ns read_classvar_from_instance
24.6 ns read_instancevar
20.8 ns read_instancevar_slots
18.0 ns read_namedtuple
28.1 ns read_boundmethod
Variable and attribute write access:
4.4 ns write_local
4.9 ns write_nonlocal
16.6 ns write_global
45.7 ns write_classvar
38.7 ns write_instancevar
25.8 ns write_instancevar_slots
Data structure read access:
19.9 ns read_list
20.4 ns read_deque
20.3 ns read_dict
18.5 ns read_strdict
Data structure write access:
20.8 ns write_list
22.8 ns write_deque
24.6 ns write_dict
23.3 ns write_strdict
Stack (or queue) operations:
50.1 ns list_append_pop
42.8 ns deque_append_pop
44.7 ns deque_append_popleft
Timing loop overhead:
0.3 ns loop_overhead
無事、それっぽい結果になりました。
謎
何が違うのかはわかりませんでしたが、こういう事もあるんですね、ということで。
もし詳しい方が居たら教えてください。