为什么"来自[Module]导入[Something]"花费更多的时间而不是" import [Module"

时间:2013-08-09 19:08:01

标签: python timeit

我使用python -mtimeit进行测试,发现from Module import Sthimport Module相比需要更多时间

E.g。

$ python -mtimeit "import math; math.sqrt(4)"
1000000 loops, best of 3: 0.618 usec per loop
$ python -mtimeit "from math import sqrt; sqrt(4)"
1000000 loops, best of 3: 1.11 usec per loop

与其他情况相同。有人可以解释背后的理由吗?谢谢!

2 个答案:

答案 0 :(得分:2)

这里有两个问题。第一步是确定哪个部分更快:import语句或调用。

所以,让我们这样做:

$ python -mtimeit 'import math'
1000000 loops, best of 3: 0.555 usec per loop
$ python -mtimeit 'from math import sqrt'
1000000 loops, best of 3: 1.22 usec per loop
$ python -mtimeit -s 'from math import sqrt' 'sqrt(10)'
10000000 loops, best of 3: 0.0879 usec per loop
$ python -mtimeit -s 'import math' 'math.sqrt(10)'
10000000 loops, best of 3: 0.122 usec per loop

(那就是我的笔记本电脑上的OS X 10.6.4上的Apple CPython 2.7.2 64位。但是同一台笔记本电脑上的python.org 3.4开发和Linux机器上的3.3.1开发结果大致相同。使用PyPy,更智能的缓存使得无法测试,因为所有内容都在1ns完成......无论如何,我认为这些结果可能与microbenchmarks一样可移植。)

事实证明,import语句的速度是其两倍多;之后,调用函数有点慢,但还不足以弥补更便宜的import。 (请记住,您的测试每次调用都会import。在现实生活中的代码中,您往往会按import调用一次以上的内容。所以,我们是真正关注一个很少会影响实际代码的边缘情况。但只要你记住这一点,我们就会继续。)


从概念上讲,您可以理解为什么from … import语句需要更长时间:它还有更多工作要做。第一个版本必须找到模块,必要时编译它并执行它。第二个版本必须完成所有这些,然后提取sqrt并将其插入到当前模块的全局变量中。所以,它必须至少有点慢。

如果你查看字节码(例如,使用dis模块并调用dis.dis('import math')),这正是区别。比较:

  0 LOAD_CONST               0 (0) 
  3 LOAD_CONST               1 (None) 
  6 IMPORT_NAME              0 (math) 
  9 STORE_NAME               0 (math) 
 12 LOAD_CONST               1 (None) 
 15 RETURN_VALUE         

......来:

  0 LOAD_CONST               0 (0) 
  3 LOAD_CONST               1 (('sqrt',)) 
  6 IMPORT_NAME              0 (math) 
  9 IMPORT_FROM              1 (sqrt) 
 12 STORE_NAME               1 (sqrt) 
 15 POP_TOP              
 16 LOAD_CONST               2 (None) 
 19 RETURN_VALUE         

额外的堆栈操作(LOAD_CONSTPOP_TOP)可能没什么区别,使用STORE_NAME的不同参数根本不重要......但是{ {1}}是一个重要的额外步骤。


令人惊讶的是,对IMPORT_FROM代码进行简要分析的快速和肮脏尝试表明,大部分成本实际上都在查找要导入的相应全局变量。我不确定为什么,但是......这意味着导入一大堆名称应该不比仅导入一个名称慢得多。并且,正如您在评论中指出的那样,这正是您所看到的。 (但是请不要过多地阅读。IMPORT_FROM可能有很大的常数因子,只有一个很小的线性因素,而且我们并没有完全抛出大量的名字。


最后一件事:如果这真的在真实代码中确实很重要,并且你想要充分利用这两个世界,那么IMPORT_FROMimport math; sqrt = math.sqrt更快,但是给你同样的小加速查询/通话时间。 (但同样,我无法想象任何真实的代码,这将是重要的。你唯一需要关心from math import sqrt需要多长时间,当你称它为十亿次,此时你赢了'请注意导入需要多长时间。另外,如果您确实需要对其进行优化,请创建一个本地范围并绑定sqrt以完全避免全局查找。)

答案 1 :(得分:0)

这不是答案,而是一些信息。它需要格式化,所以我没有把它作为评论。这是'from math import sqrt'的字节码:

>>> from math import sqrt
>>> import dis
>>> def f(n): return sqrt(n)
... 
>>> dis.dis(f)
  1           0 LOAD_GLOBAL              0 (sqrt)
              3 LOAD_FAST                0 (n)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE        

对于'导入数学'

>>> import math
>>> import dis
>>> dis.dis(math.sqrt)
>>> def f(n): return math.sqrt(n)
... 
>>> dis.dis(f)
  1           0 LOAD_GLOBAL              0 (math)
              3 LOAD_ATTR                1 (sqrt)
              6 LOAD_FAST                0 (n)
              9 CALL_FUNCTION            1
             12 RETURN_VALUE        

有趣的是,更快的方法还有一条指令。