众所周知,python以简单闻名,以速度慢而臭名,而其慢的原因,无非就是以下3中:
1.GIL
2.解释型语言
3.动态类型
1.GIL是python的一个全局锁,使得python解释器每次只能运行一个线程的字节码,限制了运用多线程的能力(仅限于Cython);
2.解释性语言没什么好说的,边解释边执行,运行速度肯定小于编译后在运行的语言;
3.由于python不用声明变量类型,有动态类型的机制,动态类型导致开销巨大,导致其速度慢。
本文就带领大家做个实验,分析使用原生python和python调用C/C++的运行时间对比分析,分析的例子是计算矩阵按行的平均值,数据的本质也就是
矩阵嘛,看看是否像大家所说的python有那么慢。
-
原生python方式,文件命名为row_mean_py.py
import numpy as np def row_mean_py(A): out = np.zeros(A.shape[0]) for i in range(A.shape[0]): for j in range(A.shape[1]): out[i] = out[i] + A[i,j] / A.shape[1] return out def row_mean_py_raw(A): return np.max(A,axis=1)
其中函数row_mean_py是使用python自定义方式计算矩阵按行的平均值,row_mean_py_raw是使用python向量化方法计算。让我们来看一下运行时间对比。
文件命名为row_mean_main.py
import timeit import numpy as np setup = """ from row_mean_py import row_mean_py import numpy as np A = np.random.normal(size=[1000,1000]) """ print("Python1 version:" , timeit.timeit(stmt="row_mean_py(A)", setup=setup, number=10)) setup = """ from row_mean_py import row_mean_py_raw import numpy as np A = np.random.normal(size=[1000,1000]) """ print("Python2 version:" , timeit.timeit(stmt="row_mean_py_raw(A)", setup=setup, number=10))结果
Python1 version: 6.4014764
Python2 version: 0.005260500000000334我们可以看到,如果还是用C/C++的思想去做数据的矩阵运算,那么代码的运行时间是多么的慢!
如果充分利用Python的向量化计算的思想去写代码,那么代码的运行时间可以大大提高!此处可以提高99.92%。 -
直接使用Cpython,文件命名为row_mean_cy.pyx
import numpy as np import cython @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) def row_mean_cy(A): A = A.astype(np.intc) out = np.zeros(A.shape[0], dtype=np.double) cdef double[:] out_view = out cdef int[:,:] A_view = A cdef Py_ssize_t i cdef Py_ssize_t j cdef int m = A.shape[0] cdef int n = A.shape[1] for i in range(m): for j in range(n): out_view[i] = out_view[i] +
(A_view[i,j]) / n return out 编写setup文件,命名为setup_row_mean_py.py
from setuptools import setup from Cython.Build import cythonize setup( ext_modules = cythonize("row_mean_cy.pyx") )使用python setup_row_mean_py.py install 命令安装即可。
最后在row_mean_main.py加入如下代码并测试
from row_mean_cy import row_mean_cy setup = """ from row_mean_cy import row_mean_cy import numpy as np A = np.random.normal(size=[1000,1000]) """ print("Cython version:" , timeit.timeit(stmt="row_mean_cy(A)", setup=setup, number=10))结果:(每次运行代码的运行时间稍微不同,但大致一致)
Python1 version: 6.3904975
Python2 version: 0.008381500000000486
Cython version: 0.04388170000000002可以看到,直接使用Cython也可以提高代码的运行时间,毕竟Cython是把python代码转化成了C/C++的代码然后运行的。
此处可以提高99.31%,而利用Python的向量化计算的思想去写代码,此处同样可以提高99.90%,两者提高的比例相差不大。这里我们可以得到初步结论,如果用C/C++的思想去写python,那么代码的运行速度是非常慢的;
如果充分利用Python的向量化计算语法,那么代码的运行速度是最快的;
如果用C/C++的代码让python调用(这里使用的是让Cython生成C/C++代码),那么运行速度也是非常快的,堪比Python的向量化计算语法。 -
读者可能就会问了,你这里使用的是Cython,生成的是最优的C/C++代码,但是实际写的C/C++并没有经过Cython优化,那么程序运行时间还能
提高那么多吗?这里我就展示一下调用原生C/C++代码,然后再对比一下运行时间。编写c代码,文件命名为row_mean.c
#include
double* row_mean_c(const int m, const int n, double *A, double *out) { int i, j; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) { out[i] = out[i] + A[i * n + j] / n; } } return out; } gcc编译成共享库,gcc -shared -o row_mean.so -fPIC row_mean.c
最后在row_mean_main.py加入如下代码并测试
setup = """ from ctypes import cdll,c_double import numpy as np cur = cdll.LoadLibrary('C:/Users/1000260871yanli/Desktop/python&c/row_mean.so') m = 1000 n = 1000 out_ = [0] * m out = (c_double * len(out_))(*out_) A_ = np.random.normal(size=[m,n]) line = c_double * n data_type = line * m A = data_type() for i in range(m): for j in range(n): A[i][j] = c_double(A_[i,j]) """ print("C version:" , timeit.timeit(stmt="cur.row_mean_c(m,n,A,out)", setup=setup, number=10)结果:(每次运行代码的运行时间稍微不同,但大致一致)
Python1 version: 6.7283076
Python2 version: 0.005481800000000092
Cython version: 0.035840799999999895
C version: 0.028557099999999558可以看到,不经过Cython优化的C/C++代码和经过Cython优化的代码,运行时间差不多,此处可以提升99.57%,Cython此处同样可以提高99.46%。
总结:
- 如果充分利用python的向量化计算,python最快,千万不要用C/C++的思想写python,堪比龟速;
- 使用Cython和写C/C++代码再让python调用这两种方式运行速度差不多,并且他们的速度堪比python的向量化计算;
- 还有很多Python与C/C++的接口,比如boost库、pybind等。
限制:
- 本文的运行速度对比分析针对的只是矩阵运算中比较简单的运算,并没有完整的去对比矩阵运算;
- 还应该对比分析除了矩阵运算其他运算。
关注我,带你走进数据算法的世界。
- 微信公众号 数据算法小屋
- CSDN https://blog.csdn.net/TommyLi_YanLi
- 知乎 https://www.zhihu.com/people/74-25-40-76-26
- 作者邮箱:rocklee_lol@hotmail.com



