我正在编写一个井字游戏并使用Enum来表示三个结果 - lose
,draw
和win
。我认为使用字符串("lose", "win", "draw")
来表示这些值会更好。但使用枚举给我带来了显着的性能影响。
这是一个最小的示例,我只是引用Result.lose
或文字字符串lose
。
import enum
import timeit
class Result(enum.Enum):
lose = -1
draw = 0
win = 1
>>> timeit.timeit('Result.lose', 'from __main__ import Result')
1.705788521998329
>>> timeit.timeit('"lose"', 'from __main__ import Result')
0.024598151998361573
这比简单地引用全局变量慢得多。
k = 12
>>> timeit.timeit('k', 'from __main__ import k')
0.02403248500195332
我的问题是:
答案 0 :(得分:10)
您正在计时定时循环。字符串文字本身完全被忽略:
>>> import dis
>>> def f(): "lose"
...
>>> dis.dis(f)
1 0 LOAD_CONST 1 (None)
3 RETURN_VALUE
这是一个什么都不做的功能。因此,定时循环需要0.024598151998361573
秒才能运行100万次。
在这种情况下,字符串实际上成为f
函数的文档字符串:
>>> f.__doc__
'lose'
但是CPython通常会在代码中省略字符串文字,如果没有分配或者是表达式的一部分:
>>> def f():
... 1 + 1
... "win"
...
>>> dis.dis(f)
2 0 LOAD_CONST 2 (2)
3 POP_TOP
3 4 LOAD_CONST 0 (None)
7 RETURN_VALUE
此处1 + 1
折叠为常量(2
),字符串文字再次消失。
因此,您无法将此与查找enum
对象上的属性进行比较。是的,查找属性需要循环。但查找另一个变量也是如此。如果您真的担心性能,可以随时缓存属性查找:
>>> import timeit
>>> import enum
>>> class Result(enum.Enum):
... lose = -1
... draw = 0
... win = 1
...
>>> timeit.timeit('outcome = Result.lose', 'from __main__ import Result')
1.2259576459764503
>>> timeit.timeit('outcome = lose', 'from __main__ import Result; lose = Result.lose')
0.024848614004440606
在timeit
测试中,所有变量都是本地变量,因此Result
和lose
都是本地查找。
enum
属性查找比“常规”属性查找需要更多时间:
>>> class Foo: bar = 'baz'
...
>>> timeit.timeit('outcome = Foo.bar', 'from __main__ import Foo')
0.04182224802207202
这是因为enum
元类包括每次查找属性时调用的specialised __getattr__
hook; enum
类的属性在专用字典而不是类__dict__
中查找。执行该钩子方法和附加属性查找(访问地图)都需要额外的时间:
>>> timeit.timeit('outcome = Result._member_map_["lose"]', 'from __main__ import Result')
0.25198313599685207
>>> timeit.timeit('outcome = map["lose"]', 'from __main__ import Result; map = Result._member_map_')
0.14024519600206986
在Tic-Tac-Toe游戏中,您通常不会担心无关紧要的时间差异。当人类播放器比计算机慢几个数量级时。人类玩家不会注意到1.2微秒或0.024微秒之间的差异。