数据科学家的常见工具--python调用C/C++

众所周知,python以简单闻名,以速度慢而臭名,而其慢的原因,无非就是以下3中:
1.GIL
2.解释型语言
3.动态类型

1.GIL是python的一个全局锁,使得python解释器每次只能运行一个线程的字节码,限制了运用多线程的能力(仅限于Cython);
2.解释性语言没什么好说的,边解释边执行,运行速度肯定小于编译后在运行的语言;
3.由于python不用声明变量类型,有动态类型的机制,动态类型导致开销巨大,导致其速度慢。

本文就带领大家做个实验,分析使用原生python和python调用C/C++的运行时间对比分析,分析的例子是计算矩阵按行的平均值,数据的本质也就是
矩阵嘛,看看是否像大家所说的python有那么慢。


  1. 原生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%。

  2. 直接使用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的向量化计算语法。

  3. 读者可能就会问了,你这里使用的是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%。


总结:

  1. 如果充分利用python的向量化计算,python最快,千万不要用C/C++的思想写python,堪比龟速;
  2. 使用Cython和写C/C++代码再让python调用这两种方式运行速度差不多,并且他们的速度堪比python的向量化计算;
  3. 还有很多Python与C/C++的接口,比如boost库、pybind等。

限制:

  1. 本文的运行速度对比分析针对的只是矩阵运算中比较简单的运算,并没有完整的去对比矩阵运算;
  2. 还应该对比分析除了矩阵运算其他运算。

关注我,带你走进数据算法的世界。

  • 微信公众号 数据算法小屋
  • CSDN https://blog.csdn.net/TommyLi_YanLi
  • 知乎 https://www.zhihu.com/people/74-25-40-76-26
  • 作者邮箱:[email protected]

你可能感兴趣的:(算法,大数据,python)