Python 性能调优 代码加速

代码逻辑优化

参考 https://zhuanlan.zhihu.com/p/152343123

利用缓存进行提速

lru_cache

使用方法简介

程序的核心或性能的瓶颈往往就是那么几个函数或语句,当你发现同一组参数会频繁地传入到这些函数里的时候,你就要考虑设一个缓存了。
比如说小明写了一个函数叫”add”,它接收两个浮点数或整数作为参数,返回这两个数的和。但是由于小明写的”add”函数算法异常复杂,甚至需要1秒来计算。
因为我们知道,一个数加另一个数的结果肯定是确定的,也就是说,你只要算出一次这个结果,下次再出现这两个值的时候,你完全可以直接用上次的计算结果。
这时,我们可以直接使用 lru_cache 来帮我们进行历史分析数据结果的缓存。lru_cache,是一个提供缓存功能的装饰器,可以缓存函数历史处理结果遇到相同的输入直接反馈历史分析结果
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In [1]: from functools import lru_cache

In [2]: @lru_cache()
...: def add(a, b):
...: from time import sleep
...: sleep(1)
...: return a+b
...:

In [3]: %time add(1,1) # 第一次执行,因为"计算量大",所以比较慢
Wall time: 1.01 s
Out[3]: 2

In [4]: %time add(1,1) # 第二次执行,因为结果已经被缓存了,所以很快
Wall time: 0 ns
Out[4]: 2

局限性

缓存机制的底层原理,是通过缓存存储结果,避免结果的重复计算,所以当数据集中,函数运行的输入参数并非存在高比例重复时,该方法则不能有效进行提速。

编译函数

numba

使用方法简介

python之所以慢,是因为它是靠CPython编译的。numba是一款可以将python函数编译为机器代码的JIT编译器,经过numba编译的python代码(仅限数组运算),其运行速度可以接近C或FORTRAN语言。
使用numba非常简单,只需要将numba装饰器应用到python函数中,无需改动原本的python代码,numba会自动完成剩余的工作。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import numba
from numba import jit

@jit(nopython=True) # jit,numba装饰器中的一种
def go_fast(a): # 首次调用时,函数被编译为机器代码
trace = 0
# 假设输入变量是numpy数组
for i in range(a.shape[0]): # Numba 擅长处理循环
trace += np.tanh(a[i, i])
return a + trace

在numba加速下,代码执行时间为3.63微秒/循环。不经过numba加速,代码执行时间为136微秒/循环,两者相比,前者快了40倍。

局限性

numba不会对numpy和for循环以外的python代码有很大帮助。

Taichi

Taichi 是一种嵌在 Python 中的并行编程语言,使用 Python 语言作为 DSL,所以我们可以在正常的 Python 代码中使用它。它可以帮助我们轻松编写可移植的高性能并行程序,专注于高性能计算和图形领域。安装使用上和 Numba 类似。

Codon

使用方法简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import codon
from time import time

def is_prime_python(n):
if n <= 1:
return False
for i in range(2, n):
if n % i == 0:
return False
return True

@codon.jit
def is_prime_codon(n):
if n <= 1:
return False
for i in range(2, n):
if n % i == 0:
return False
return True

t0 = time()
ans = sum(1 for i in range(100000, 200000) if is_prime_python(i))
t1 = time()
print(f'[python] {ans} | took {t1 - t0} seconds')
# [python] 8392 | took 39.6610209941864 seconds

t0 = time()
ans = sum(1 for i in range(100000, 200000) if is_prime_codon(i))
t1 = time()
print(f'[codon] {ans} | took {t1 - t0} seconds')
# [codon] 8392 | took 0.998633861541748 seconds

更换解释器

pypy

使用方法简介

去pypy官网的下载页面,选择合适的版本进行下载。下载好后,解压缩,就可以使用了。(建议将pypy添加到path环境变量里,这样使用起来会方便很多)

pypy是一个Python解释器,兼容Python,却能比Python更快,使用pypy做解释器,几乎不需要改动代码,就可以实现加速。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time
def work():
l = list(range(1, 10000001))
tmp = []
for i in l:
if i % 2 == 0:
tmp.append(i)
for i in range(len(tmp)):
tmp[i] = tmp[i] * 2
tmp.reverse()
return sum(tmp)
t = time.time()
work()
print(time.time()-t)

反正就是各种循环,然后测用时。

接着把这个程序存进a.py,然后分别使用pypy和python解释器执行测试速度。
pypy执行:

1
2
$ pypy3 a.py
0.1890418529510498

Python解释器执行:

1
2
$ python a.py
0.9882152080535889

-------------本文结束感谢您的阅读-------------