3

我曾经python -mtimeit测试并发现它需要更多时间来from Module import Sth比较import Module

例如

$ 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

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

4

2 回答 2

2

这里有两个问题。第一步是找出哪个部分更快:导入语句或调用。

所以,让我们这样做:

$ 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 dev 和 linux 机器上的 3.3.1 给出了大致相似的结果。使用 PyPy,更智能的缓存无法进行测试,因为一切都在 1ns 内完成……无论如何,我认为这些结果可能与微基准测试一样便携。)

所以事实证明,该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_CONSTand POP_TOP)可能没有太大区别,并且使用不同的参数 toSTORE_NAME根本不太重要……但这IMPORT_FROM是一个重要的额外步骤。


令人惊讶的是,对代码进行快速而肮脏的尝试IMPORT_FROM表明,大部分成本实际上是查找要导入的适当全局变量。我不知道为什么,但是……这意味着导入大量名称应该不会比只导入一个名称慢很多。而且,正如您在评论中指出的那样,这正是您所看到的。(但不要过多解读。有很多原因IMPORT_FROM可能有一个很大的常数因子,而只有一个很小的线性因子,而且我们并没有完全给出大量的名字。)


最后一件事:如果这在实际代码中真的很重要,并且您想要两全其美,那么import math; sqrt = math.sqrt它比from math import sqrt. (但同样,我无法想象任何真正重要的代码。唯一一次你会关心需要多长时间sqrt是当你调用它十亿次时,你不会关心多久导入需要。另外,如果您确实需要优化它,请创建一个本地范围并sqrt在那里绑定以完全避免全局查找。)

于 2013-08-09T19:14:19.350 回答
0

这不是答案,而是一些信息。它需要格式化,所以我没有将它作为评论包含在内。这是“从数学导入 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        

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

于 2013-08-09T19:16:29.310 回答