如何使用Python 3.4的枚举没有明显的减速?

Eli*_*ICA 8 python performance enums python-3.4

我在写一个井字棋游戏,并使用枚举来表示三种结果- lose,drawwin.我认为这比使用字符串("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
Run Code Online (Sandbox Code Playgroud)

这比简单地引用全局变量要慢得多.

k = 12

>>> timeit.timeit('k', 'from __main__ import k')
0.02403248500195332
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  • 我知道全局查找比Python中的本地查找要慢得多.但为什么枚举查找更糟糕?
  • 如何在不牺牲性能的情况下有效使用枚举?枚举查找结果完全支配了我的tic-tac-toe程序的运行时间.我们可以在每个函数中保存枚举的本地副本,或者将所有内容都包装在一个类中,但这两个看起来都很尴尬.

Mar*_*ers 10

你正在计时定时循环.完全忽略字符串文字:

>>> import dis
>>> def f(): "lose"
... 
>>> dis.dis(f)
  1           0 LOAD_CONST               1 (None)
              3 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

这是一个什么也不做的函数在所有.因此,定时循环需要0.024598151998361573几秒钟才能运行100万次.

在这种情况下,字符串实际上成为f函数的文档字符串:

>>> f.__doc__
'lose'
Run Code Online (Sandbox Code Playgroud)

但是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        
Run Code Online (Sandbox Code Playgroud)

在这里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
Run Code Online (Sandbox Code Playgroud)

timeit测试中所有变量都是本地人,所以无论Resultlose本地查找.

enum 属性查找确实比"常规"属性查找花费更多时间:

>>> class Foo: bar = 'baz'
... 
>>> timeit.timeit('outcome = Foo.bar', 'from __main__ import Foo')
0.04182224802207202
Run Code Online (Sandbox Code Playgroud)

那是因为enum元类包含每次查找属性时调用的专用__getattr__钩子 ; 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
Run Code Online (Sandbox Code Playgroud)

在Tic-Tac-Toe的游戏中,您通常不会担心无关紧要的时间差异.当人类播放器比计算机慢几个数量级时.人类玩家不会注意到1.2微秒或0.024微秒之间的差异.