Python中函数查找操作的时间复杂度是多少

时间:2015-01-06 05:18:59

标签: python time-complexity lookup

我想知道,因为一个常见的优化策略是在变量中“缓存”查找,然后使用该变量调用方法/函数,查找操作有多贵?

我的意思是“缓存”查找,以防它不是正确的术语:

class TestClass:

    def myMethod(self):
       printMethod = self.printMethod
       for i in range(0, 1000):
          printMethod(i)

    def printMethod(self, i):
       print i

3 个答案:

答案 0 :(得分:6)

节省的时间并不复杂,而是实际时间。在命名空间中查找函数名称只是查找字典中的键,该字典已经是O(1)。查找对象上的属性也是字典查找,也是O(1)。有一个优化的操作码用于按名称查找局部变量,但仍然不能比O(1)快。

在您的示例中,查找self.printMethod会查找本地(self),然后查找属性(printMethod)。这是两次查找。如果将它存储在本地,那么对局部变量printMethod的每次后续访问只是一次查找而不是两次。那仍然是O(1),但它更快,因为它是一个较小的常数。

This question进一步讨论了名称查找在Python中的工作原理。

答案 1 :(得分:4)

以下是一些可用于计算差异的代码:

http://pastebin.com/svBN5NZ9

还有一些时间安排结果:

In [2]: %timeit Class1().runCached(10000)
1000 loops, best of 3: 1.74 ms per loop

In [3]: %timeit Class1().runNormal(10000)
100 loops, best of 3: 2.92 ms per loop

In [4]: %timeit Class10().runCached(10000)
1000 loops, best of 3: 1.7 ms per loop

In [5]: %timeit Class10().runNormal(10000)
100 loops, best of 3: 6.01 ms per loop

In [6]: %timeit Class100().runCached(10000)
1000 loops, best of 3: 1.7 ms per loop

In [7]: %timeit Class100().runNormal(10000)
10 loops, best of 3: 42.9 ms per loop

因此,一般来说,缓存方法的速度更快,方法查找时间取决于类继承层次结构的深度。

但请注意,如果您使用像pypy这样的跟踪JIT,可能会得到不同的结果,因为跟踪可能会为您有效地缓存方法指针。

答案 2 :(得分:3)

两次O(1)操作可能需要非常不同的时间。实例属性查找(self.printMethod)和局部变量都是O(1),但是局部变量访问被优化,因此不需要字典查找,因此它更快。查看字节码,以便在CPython中访问本地变量 vs 一个实例变量:

>>> import dis
>>> class MyClass:
...   def printMethod(self):
...     pass
...   def code(self):
...     pm = self.printMethod
...     self.printMethod()
...     pm()
...
>>> dis.dis(MyClass.code)
  5           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (printMethod)
              6 STORE_FAST               1 (pm)

  6           9 LOAD_FAST                0 (self)
             12 LOAD_ATTR                0 (printMethod)
             15 CALL_FUNCTION            0
             18 POP_TOP

  7          19 LOAD_FAST                1 (pm)
             22 CALL_FUNCTION            0
             25 POP_TOP
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE
>>>

您可以看到访问pm需要进行简单的LOAD_FAST操作,该操作会从本地堆栈框架中的固定数字offest加载值,而访问self.printMethod则需要额外的LOAD_ATTR {{1}} 1}}操作。

当然,建立局部变量的值确实需要时间,因此必须多次使用(就像在代码示例中一样)才能看到任何性能优势。

正如@ user5402指出的那样,由于编译器的优化,您的里程可能因您使用的实现而有所不同。