Python中函数查找操作的时间复杂度是多少
我想知道,既然一种常见的优化策略是将查找“缓存”在变量中,然后使用该变量调用方法/函数,那么查找操作的成本有多高 这就是我所说的“缓存”查找的意思,以防它不是正确的术语:Python中函数查找操作的时间复杂度是多少,python,time-complexity,lookup,Python,Time Complexity,Lookup,我想知道,既然一种常见的优化策略是将查找“缓存”在变量中,然后使用该变量调用方法/函数,那么查找操作的成本有多高 这就是我所说的“缓存”查找的意思,以防它不是正确的术语: class TestClass: def myMethod(self): printMethod = self.printMethod for i in range(0, 1000): printMethod(i) def printMethod(self,
class TestClass:
def myMethod(self):
printMethod = self.printMethod
for i in range(0, 1000):
printMethod(i)
def printMethod(self, i):
print i
节省的不是时间复杂性,而是实际时间。在命名空间中查找函数名只是在字典中查找一个键,它已经是O(1)。查找对象上的属性也是dict查找,也是O(1)。有一个优化的操作码用于按名称查找局部变量,但仍然不能比O(1)快 在您的示例中,查找
self.printMethod
将查找本地(self
),然后查找属性(printMethod
)。这是两次查找。如果将其存储在局部变量中,则对局部变量printMethod
的每次后续访问都只是一次查找,而不是两次。这仍然是O(1),但它更快,因为它是一个更小的常数
进一步讨论了Python中名称查找的工作原理。以下是一些代码,您可以使用这些代码来计算时间差: 以及一些计时结果:
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,可能会得到不同的结果,因为跟踪可能会有效地为您缓存方法指针。两个O(1)操作可能需要非常不同的时间。实例属性查找(self.printMethod
)和局部变量都是O(1),但局部变量访问已优化,因此不需要字典查找,因此速度更快。查看访问CPython中的局部变量与实例变量的字节码:
>>> 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\u FAST
操作,该操作在访问self时从本地堆栈帧中的固定数字offest加载一个值。printMethod
需要一个额外的LOAD\u ATTR
操作
当然,建立局部变量的值确实需要时间,因此必须多次使用它(就像在代码示例中一样),才能看到任何性能优势
正如@user5402所指出的,由于编译器部分的优化,您的里程数可能会根据您使用的实现而有所不同。您是在问这种方法以这种方式缓存时查找它的成本有多高,还是不以这种方式缓存时查找它的成本有多高?用big-O术语来说,它是O(1)。这就是你想知道的吗?我假设他们有不同的时间复杂度,因为两种方法的计时不同。