目录

1.使用属性访问调用方法会有额外时间开销

2. 局部变量会比全局变量运行速度快

3. 使用函数比没有结构的代码要快

4. 使用额外的处理层(比如装饰器、属性访问、描述器)包装代码会使程序运行变慢

5. 内置的容器更快

6. 避免创建不必要的数据结构或复制


在优化之前要看的:14.14 加速程序运行

1.使用属性访问调用方法会有额外时间开销

每一次使用点(.)操作符来访问属性的时候会带来额外的开销。 它会触发特定的方法,如 __getattribute__() 和 __getattr__() ,这些方法会进行字典操作操作。

通常可以使用 from module import name 这样的导入形式,以及使用绑定的方法。

演示对比:

注:A1.timer 是一个计时功能的装饰器,#1. 计时功能的装饰器

from A1 import timer

import math
from math import sqrt

'''使用属性访问调用 sqrt()
'''
@timer
def compute(nums):
    result = []
    for n in range(nums):
        result.append(math.sqrt(n))
    return result

'''直接调用 sqrt()
'''
@timer
def compute_new(nums):
    result = []
    for n in range(nums):
        result.append(sqrt(n))
    return result

if __name__ == '__main__':
	compute(10000)

执行结果:

Finished compute in 0.0024616660000000012

Finished compute_new in 0.001980217999999999

可见去掉属性访问后,程序要快一点。

不过,这些改变只有在大量重复代码中才有意义,比如循环。 因此,这些优化也只是在某些特定地方才应该被使用。

2. 局部变量会比全局变量运行速度快

对于频繁访问的名称,通过将这些名称变成局部变量可以加速程序运行。额外的加速原因是因为对于局部变量的查找要快于全局变量。

演示对比:

import math
from A1 import timer

@timer
def compute(nums):
    result = []
    for n in range(nums):
        result.append(math.sqrt(n))
    return result

@timer
def compute2(nums):
    sqrt = math.sqrt
    result = []
    for n in range(nums):
        result.append(sqrt(n))
    return result

if __name__ == '__main__':
	compute(10000)

执行结果: 

Finished compute in 0.002308336000000001

Finished compute2 in 0.0019338770000000005

可见把 math.sqrt 变为局部变量后快了一点。

对于类中的属性访问也同样适用于这个原理。  通常来讲,查找某个值比如 self.name 会比访问一个局部变量要慢一些,可以在类方法中把类的属性变为局部变量。

示例:

class SomeClass:
    ...
    def method(self):
         value = self.value
         ...

3. 使用函数比没有结构的代码要快

把上面演示的 compute(nums)改为有结构的代码:

result = []
    for n in range(10000):
        result.append(math.sqrt(n))
    return result

像这样定义在全局范围的代码运行起来要比定义在函数中运行慢的多。局部变量会比全局变量运行速度快。

速度的差异取决于实际运行的程序,不过根据经验,使用函数带来15-30%的性能提升是很常见的。

4. 使用额外的处理层(比如装饰器、属性访问、描述器)包装代码会使程序运行变慢

任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装你的代码时,都会让程序运行变慢。 比如看如下的这个类:

>>> class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    @property
    def y(self):
        return self._y
    @y.setter
    def y(self, value):
        self._y = value

        
>>> a = A(1,2)
>>> 
>>> from timeit import timeit
>>> 
>>> timeit('a.x', 'from __main__ import a')
0.0482237750000003
>>> 
>>> timeit('a.y', 'from __main__ import a')
0.1961520250000035

可以看到,访问属性y相比属性x而言慢的不止一点点,大概慢了4.5倍。

如果你在意性能的话,那么就需要重新审视下对于y的属性访问器的定义是否真的有必要了。 如果没有必要,就使用简单属性吧。 如果仅仅是因为其他编程语言需要使用getter/setter函数就去修改代码风格,这个真的没有必要。

5. 内置的容器更快

内置的数据类型比如字符串、元组、列表、集合和字典都是使用C来实现的,运行起来非常快。

如果你想自己实现新的数据结构(比如链接列表、平衡树等), 那么要想在性能上达到内置的速度几乎不可能,因此,还是乖乖的使用内置的吧。

6. 避免创建不必要的数据结构或复制

尽量避免