引言:最近需要对一个算法进行并行加速,最初使用python实现的,也尝试了用python中的多线程进行加速,后来才发现,python中的threading
受制于GIL,同时只能使用一个核进行运算,所以搞了半天最后发现多线程和非并行算法在运行时间上无差别。当然我也尝试了multiprocessing
模块,但我那个算法不适合用多进程的方法加速,反而会导致程序运行时间变长,所以我就只好退而求其次,需要加速的部分用C语言重写,并通过C的多线程来进行加速。所以就涉及到了python调用C函数的问题。因为需要将python中的numpy数组传递到C函数中,为了实现这一部分也花了两个晚上才折腾明白,所以记录下来,我自己也是刚学这一部分,可能无法写的特别精致到位,只是把我自己觉得有用的部分记录。
ctypes是python提供的一个用来调用C函数的模块。在网上看到很多人都建议用ctypes的方法在python中调用C的动态链接库。具体关于ctypes的介绍可以去官网上看一下。
from ctypes import *
2.1 编写C函数
由于ctypes的功劳,c函数部分按照正常写c的方法写即可。下面是我测试时写的一个c文件:
//ctype_test.c
#include
void onedemiarr(const int * a, int n, int * b){
puts("onedemiarr starts!\n");
int i;
for(i = 0; i < n; i++){
b[i] = a[i] * 2;
}
puts("onedemiarr ends!");
return;
}
void twodemiarr(const int (*a)[3], int m, int n, int (*b)[3]){
puts("twodemiarr starts!\n");
int i, j;
for(i = 0; i < m; i++){
for(j = 0; j < n; j++){
b[i][j] = a[i][j] * 2;
}
}
puts("twodemiarr ends!\n");
return;
}
函数onedemiarr
, twodemiarr
均为将数组a的内容乘以2放入数组b中。
在这里想说的是,写完C代码后,一定要都测试一下自己的C函数写的是否正确,是否可以通过main函数来正常调用,不然不经测试即在python中使用,很可能出错了也没办法发现是C函数本身的错(我自己就是这么被自己坑过来的)。
2.2 编译动态库
使用gcc命令将c函数编译为动态链接库,因为我是在windows下写的,所以编译成.dll格式,如果是linux下就是.so格式了。
gcc -shared ctype_test.c -o ctest.dll //-shared说明这是一个动态库
需要注意的是,生成的动态链接库如果是32位的(根据电脑上的MinGW位数而定),则只能被32位的python调用,如果用64位的python调用32位的动态链接库会报错。
当我们编译好动态链接库后,主要的工作就在python部分了。python中需要做的事情有:引入动态链接库,定义参数,申明动态链接库中函数的参数格式,调用C函数。
#ctype_test.py
from ctypes import *
import numpy as np
import numpy.ctypeslib as npct
if __name__ == '__main__':
arr_1 = np.array([1,2,3,4], dtype = np.int)
arr_2 = np.ndarray((4,), dtype = np.int)
arr_3 = np.array([[1,2,3],[4,5,6]], dtype = np.int)
arr_4 = np.ndarray((2,3), dtype = np.int)
lib = npct.load_library("ctest",".") #引入动态链接库,load_library的用法可参考官网文档
'''
申明函数onedemiarr, twodemiarr的传入参数类型;ndpointer为numpy.ctypeslib扩展库中提供的函数,
可将numpy数组转为指针形式被C函数识别, 其中ndim表示数组维数,还有一个shape参数(可选)表示数组格式,
flags = "C_CONTIGUOUS" 表示是和C语言相似的数组存放方式。
'''
lib.onedemiarr.argtypes = [npct.ndpointer(dtype = np.int, ndim = 1, flags="C_CONTIGUOUS"),
c_int, npct.ndpointer(dtype = np.int, ndim = 1, flags="C_CONTIGUOUS")]
lib.twodemiarr.argtypes = [npct.ndpointer(dtype = np.int, ndim = 2, shape = (2, 3), flags="C_CONTIGUOUS"),
c_int, c_int, npct.ndpointer(dtype = np.int, ndim = 2, shape = (2,3), flags="C_CONTIGUOUS")]
lib.onedemiarr(arr_1, c_int(4), arr_2) #函数调用
print(arr_1,arr_2) #将数组内容打印出来,就会发现此时arr_2数组的内容已经被改变。
print("**----------------**")
lib.twodemiarr(arr_3, c_int(2), c_int(3), arr_4)
print(arr_3, "\n",arr_4)
确认python代码无误了,就可以执行python程序了。
python ctype_test.py
以上内容均已测试无误,测试平台为:Windows10, MinGW32位,python32位。