为什么早退比其他人慢?

mac*_*mac 174 python optimization python-2.7

这是我几天前给出的回答的后续问题.编辑:似乎该问题的OP已经使用了我发给他的代码来问同样的问题,但我没有意识到它.道歉.提供的答案虽然不同!

我基本上观察到:

>>> def without_else(param=False):
...     if param:
...         return 1
...     return 0
>>> def with_else(param=False):
...     if param:
...         return 1
...     else:
...         return 0
>>> from timeit import Timer as T
>>> T(lambda : without_else()).repeat()
[0.3011460304260254, 0.2866089344024658, 0.2871549129486084]
>>> T(lambda : with_else()).repeat()
[0.27536892890930176, 0.2693932056427002, 0.27011704444885254]
>>> T(lambda : without_else(True)).repeat()
[0.3383951187133789, 0.32756996154785156, 0.3279120922088623]
>>> T(lambda : with_else(True)).repeat()
[0.3305950164794922, 0.32186388969421387, 0.3209099769592285]
Run Code Online (Sandbox Code Playgroud)

......或换句话说:else无论if条件是否被触发,使条款更快.

我假设它与两者生成的不同字节码有关,但有人能够详细确认/解释吗?

编辑:似乎不是每个人都能重现我的时间,所以我认为在我的系统上提供一些信息可能是有用的.我正在安装默认的python运行Ubuntu 11.10 64位.python生成以下版本信息:

Python 2.7.2+ (default, Oct  4 2011, 20:06:09) 
[GCC 4.6.1] on linux2
Run Code Online (Sandbox Code Playgroud)

以下是Python 2.7中反汇编的结果:

>>> dis.dis(without_else)
  2           0 LOAD_FAST                0 (param)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 (1)
              9 RETURN_VALUE        

  4     >>   10 LOAD_CONST               2 (0)
             13 RETURN_VALUE        
>>> dis.dis(with_else)
  2           0 LOAD_FAST                0 (param)
              3 POP_JUMP_IF_FALSE       10

  3           6 LOAD_CONST               1 (1)
              9 RETURN_VALUE        

  5     >>   10 LOAD_CONST               2 (0)
             13 RETURN_VALUE        
             14 LOAD_CONST               0 (None)
             17 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

Dun*_*can 377

这是一个纯粹的猜测,我还没有想出一个简单的方法来检查它是否正确,但我有一个理论适合你.

我尝试了你的代码,得到了相同的结果,without_else()反复略慢于with_else():

>>> T(lambda : without_else()).repeat()
[0.42015745017874906, 0.3188967452567226, 0.31984281521812363]
>>> T(lambda : with_else()).repeat()
[0.36009842032996175, 0.28962249392031936, 0.2927151355828528]
>>> T(lambda : without_else(True)).repeat()
[0.31709728471076915, 0.3172671387005721, 0.3285821242644147]
>>> T(lambda : with_else(True)).repeat()
[0.30939889008243426, 0.3035132258429485, 0.3046679117038593]
Run Code Online (Sandbox Code Playgroud)

考虑到字节码是相同的,唯一的区别是函数的名称.特别是时序测试会查找全局名称.尝试重命名without_else(),差异消失:

>>> def no_else(param=False):
    if param:
        return 1
    return 0

>>> T(lambda : no_else()).repeat()
[0.3359846013948413, 0.29025818923918223, 0.2921801513879245]
>>> T(lambda : no_else(True)).repeat()
[0.3810395594970828, 0.2969634408842694, 0.2960104566362247]
Run Code Online (Sandbox Code Playgroud)

我的猜测是without_else与其他东西发生哈希冲突,globals()因此全局名称查找稍慢.

编辑:具有7或8个键的字典可能有32个插槽,因此在此基础上without_else有一个哈希冲突__builtins__:

>>> [(k, hash(k) % 32) for k in globals().keys() ]
[('__builtins__', 8), ('with_else', 9), ('__package__', 15), ('without_else', 8), ('T', 21), ('__name__', 25), ('no_else', 28), ('__doc__', 29)]
Run Code Online (Sandbox Code Playgroud)

澄清哈希如何工作:

__builtins__ 散列到-1196389688,它减少了模数表的大小(32)意味着它存储在表的#8插槽中.

without_else哈希值为505688136,减少模数32为8,因此存在碰撞.要解决此Python计算:

从...开始:

j = hash % 32
perturb = hash
Run Code Online (Sandbox Code Playgroud)

重复此操作直到找到空闲插槽:

j = (5*j) + 1 + perturb;
perturb >>= 5;
use j % 2**i as the next table index;
Run Code Online (Sandbox Code Playgroud)

这使它17用作下一个索引.幸运的是,这是免费的,所以循环只重复一次.散列表大小是2的幂,散列表的大小也是散列表的大小2**i,i是散列值使用的位数j.

进入表格的每个探针都可以找到以下其中一个:

  • 插槽为空,在这种情况下探测停止,我们知道该值不在表中.

  • 该插槽未使用但过去曾使用过,在这种情况下我们会尝试按上面计算的下一个值.

  • 插槽已满,但存储在表中的完整哈希值与我们正在寻找的密钥的哈希值不同(这就是__builtins__vs 的情况without_else).

  • 插槽已满并且具有我们想要的哈希值,然后Python检查以查看我们正在查找的密钥和对象是否是同一个对象(在这种情况下,它们将是因为可能是标识符的短字符串被实现相同的标识符使用完全相同的字符串).

  • 最后,当插槽已满时,散列完全匹配,但是键不是相同的对象,然后只有这样,Python才会尝试将它们进行相等性比较.这相对较慢,但在名称查找的情况下,实际上不应该发生.

  • @Chris,没有字符串的长度不应该很重要.您第一次散列字符串时,它将花费与字符串长度成比例的时间,但随后计算的散列将缓存在字符串对象中,因此后续散列为O(1). (9认同)
  • 迷人!我可以叫你Sherlock吗?;)无论如何,我希望一旦问题符合条件,我就不会忘记给你一些奖励. (7认同)
  • @Duncan - 非常感谢您花时间来说明哈希过程.一流的答案!:) (6认同)
  • @mac,不太好.我将添加一些关于哈希分辨率的内容(将其纳入评论,但它比我想象的更有趣). (4认同)