Python 加速运算

发布于 2022-08-23  1054 次阅读


Please refresh the page if equations are not rendered correctly.
---------------------------------------------------------------

科学计算最好使用GPU加速
GPU 为多核,一般有上千核心,但是 GPU 的单核心和 CPU 处理器的单核心在性能上相比是差了很多的。待执行的任务如果是一个能非常好地进行并行化的结构表,并且每一个计算单位的计算复杂度不是特别高,我们可以通过并行化对其进行计算。这种情况下,如果我们把任务分配给 GPU 去计算的话,一般都会获得很大的效率提升。Python生态下用GPU进行数据科学计算加速的实践经验&案例

一.尽量使用numpy

A=np.random.random([5,2])
B=np.random.random([2,4])
# print(%timeit C=np.dot(A,B))
#1.34 µs ± 28.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

小矩阵可以直接把(数据)存储在CPU的cache(高速缓冲存储器),CPU可以快速访问,这个时候CPU比GPU快。但是当矩阵的行列数增大,继续使用numpy计算速度会减慢千倍。此时可以考虑用Cpython,可以比numpy快一倍左右,但仍然很慢。如此则考虑使用GPU计算。见第三节。

二.JIT

Just in time-即时编译,numba就是 jit 的一个编译器

2.1 Numba

Mumba官网
Numba是一款可以把原生的Numpy代码迁移至GPU上运行的库,而且一般情况下,仅仅需要添加一行代码:@numba.jit,(decorator)就可以了。装饰器就是函数的函数,修饰的函数体不能出现numpy库中的方法,只能是纯python代码。它们可以将其修饰的函数编译成机器码函数,并返回一个可在Python中调用机器码的包装对象。为了能将Python函数编译成能高速执行的机器码,我们需要告诉JIT编译器函数的各个参数和返回值的类型。我们可以通过多种方式指定类型信息,@jit('f8(f8[:])'),类型信息由一个字符串’f8(f8[:])’指定。其中’f8’表示8个字节双精度浮点数,括号前面的’f8’表示返回值类型,括号里的表示参数类型,’[:]’表示一维数组。因此整个类型字符串表示func()是一个参数为双精度浮点数的一维数组,返回值是一个双精度浮点数。需要注意的是,JIT所产生的函数只能对指定的类型的参数进行运算.如果希望JIT能针对所有类型的参数进行运算,可以使用autojit.autoit虽然可以根据参数类型动态地产生机器码函数,但是由于它需要每次检查参数类型,因此计算速度也有所降低。numba的用法很简单,基本上就是用jit和autojit这两个修饰器,和一些类型对象。

下面的列出numba所支持的所有类型:
print [obj for obj in nb.dict.values() if isinstance(obj,nb.minivect.minitypes.Type)][size_t, Py_uintptr_t, uint16, complex128, float, complex256, void, int , long double, unsigned PY_LONG_LONG, uint32, complex256, complex64, object_, npy_intp, const char *,double, unsigned short, float, object_, float, uint64, uint32, uint8, complex128, uint16, int, int , uint8, complex64, int8, uint64, double, long double, int32, double, long double,char, long, unsigned char, PY_LONG_LONG, int64, int16, unsigned long, int8, int16, int32,unsigned int, short, int64, Py_ssize_t]

from numba import autojit
@autojit
import numpy as np
import random
import numba
@numba.jit
def multiplication(A,B):
    return np.dot(A,B)
A=np.random.random([5,2])
B=np.random.random([2,4])
# %timeit C = multiplication(A,B)
''' The slowest run took 17.15 times longer than the fastest. 
This could mean that an intermediate result is being cached.
4.56 µs ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)'''

但是numba不是任何情况都可以提升速度的。对于轻量级,独立的元素级运算,numba可以利用GPU>CPU的优势进行提速。

如果希望使用jit加速后的函数完全不受Python解释器的干涉,我们可以使用nopython或者@njit,这种模式下将会提供一个最优的性能来优化for循环。如果代码涉及非数值处理情形,python解释器是重要的,必须避免使用该模式。

2.2 Pypy

Pypy 是一个 Python 的 JIT 编译器。Pypy 最大优势在于 Python 代码完全不用改变,就能通过 Pypy 加速。 Pypy 需要在编译的同时保持 Python 所有的语言特性,所以能够进行的优化比较有限。

三.Cpython based method

使用 Cython 编写程序实现加速也是一种常见的选择。在 Numpy 和 Scipy 的官方代码中有不少模块都是使用 Cython 编写然后编译的。但按照 Cython 的要求书写代码会比较麻烦,会牺牲一些可读性。Cython 支持一定程度的并行计算,但不支持直接调用 GPU 进行计算。

3.1 Cpython

3.2 pyculib

3.3 tensorflow

3.4 cupy显卡加速

四.Taichi

用 Taichi 加速 Python:提速 100+ 倍!

  1. Taichi 是编译性的,而 Python 是解释性的
  2. Taichi 能自动并行,而 Python 通常是单线程的
  3. Taichi 能在 GPU 上运行,而 Python 本身是在 CPU 上运行
  4. Taichi 能够直接操纵循环的每一次迭代,以一种更细的粒度进行对于计算的描述,类似 C++ 和 CUDA
!pip install taichi
ti.init()
ti.init(arch=ti.cpu) # 指定计算位置
ti.init(arch=ti.gpu)

给函数分别加上装饰器。
@ti.func 和 @ti.kernel

ti.kernel 修饰的函数内部不能用 pandas 之类的库,但是可以把计算结果在非 ti.kernel 部分随意使用。Numpy 数据也可以自由在 kernel 内调用

五.减少for的使用

尽量使用向量化函数,np.vectorlize()

Everything not saved will be lost.
最后更新于 2022-08-23