Python 为什么提前返回比其他速度慢?

Python 为什么提前返回比其他速度慢?,python,optimization,python-2.7,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 ...

这是我们的后续问题编辑:这个问题的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]
…或者换句话说:无论是否触发
条件,使用
else
子句都更快

我想这与这两种语言生成的不同字节码有关,但是有人能够详细地确认/解释吗

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

Python 2.7.2+ (default, Oct  4 2011, 20:06:09) 
[GCC 4.6.1] on linux2
以下是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        

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

我尝试了你的代码,得到了相同的结果,
不使用其他方法()
重复地比
使用其他方法()稍微慢一些。

考虑到字节码是相同的,唯一的区别是函数名。特别是计时测试会查找全局名称。尝试在不使用_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]
我的猜测是,
without\u else
globals()
中的其他内容发生哈希冲突,因此全局名称查找稍微慢一点

编辑:一个有7个或8个键的字典可能有32个插槽,因此在此基础上,没有其他键的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
有哈希冲突:

>>> [(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)]
要阐明散列的工作原理,请执行以下操作:

\uuuu内置\uuuuu
哈希为-1196389688,这减少了表大小(32)的模,意味着它存储在表的#8插槽中

without_else
散列到505688136,这减少了模32为8,因此存在冲突。要解决此问题,Python将计算:

首先是:

j = hash % 32
perturb = hash
重复此操作,直到找到可用插槽:

j = (5*j) + 1 + perturb;
perturb >>= 5;
use j % 2**i as the next table index;
这就给了它17作为下一个索引。幸运的是,这是免费的,因此循环只重复一次。哈希表大小是2的幂,因此
2**i
是哈希表的大小,
i
是从哈希值
j
使用的位数

表中的每个探针都可以找到其中一个:

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

  • 插槽未使用,但在过去使用过,在这种情况下,我们可以尝试 下一个值按上述方法计算

  • 插槽已满,但表中存储的完整哈希值未满 与我们要查找的密钥的哈希相同(这就是 发生在
    \uuu内置\uuuu
    无其他
    的情况下

  • 插槽已满,并且正好具有我们想要的哈希值,然后是Python 检查键和我们正在查找的对象是否为 相同的对象(在本例中,它们将是短字符串 这可能是因为标识符被拘留了,所以使用相同的标识符 完全相同的字符串)

  • 最后,当插槽已满时,哈希将完全匹配,但密钥 如果不是相同的对象,那么Python就会尝试 比较他们是否平等。这是相对缓慢的,但在 查找姓名的情况实际上不应该发生


上有一个相同的问题,所以我现在找不到。他们检查生成的字节码,还有一个附加步骤。观察到的差异很大程度上取决于测试仪(机器,所以…),有时只会发现非常小的差异。在3.x上,除了一些无法访问的代码(
LOAD\u CONST(None);返回值
-但如上所述,它永远不会到达)之外,它们都会生成相同的字节码,并使用
。我非常怀疑死代码能使函数更快。有人能在2.7上提供一个
dis
吗?我无法复制这个。具有
else
False
的函数速度最慢(152ns)。第二快的是
True
没有
else
(143ns),另外两个基本相同(137ns和138ns)。我没有使用默认参数,而是在iPython中使用
%timeit
测量它。我无法重现这些计时,有时with-else更快,有时这是without-else版本,看起来它们对我来说非常相似……添加了反汇编结果。我使用的是Ubuntu11.10,64位,Python2.7,配置与@mac相同。我也同意
with_else
明显更快。@Chris,不,字符串的长度不应该太长。第一次散列字符串时,它将花费与字符串长度成比例的时间,但随后计算的散列将缓存在string对象中,因此后续散列为O(1)。啊,好吧,我不知道缓存,但这很有意义!我可以叫你夏洛克吗不管怎样,我希望我不会忘记,一旦问题符合条件,我会给你额外的奖励。@mac,不完全是。我将补充一点关于哈希解析的内容(本打算将其压缩到评论中,但它比我想象的更有趣)。@Duncan-非常感谢您花时间演示哈希过程。一流的答案!:)
j = (5*j) + 1 + perturb;
perturb >>= 5;
use j % 2**i as the next table index;