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

时间:2015-06-12 22:00:29

标签: python performance enums python-3.4

我正在编写一个井字游戏并使用Enum来表示三个结果 - losedrawwin。我认为使用字符串("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

我的问题是:

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

1 个答案:

答案 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测试中,所有变量都是本地变量,因此Resultlose都是本地查找。

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微秒之间的差异。