在写一些对性能要求十分严格的功能的时候,python往往力不从心。毕竟是一个解释性的语言,没有办法和变异性的语言去比较速度和内存占用率。但是python的本身的特定就使得我们可以将耗时的操作移交给编译性的语言去实现。由于项目上的关系,需要对迸发有着比较严格的要求,所以想到了go语言。这里介绍一下如何使用python调用go语言的代码。python调用go语言是通过c语言实现的,可以将go语言打包成so,然后让python通过ctypes加载so文件来实现python调用go语言。这里介绍在linux上实现的方式,在windows上,应该需要改变编译的指令。首先是go方面的代码:main.go文件package mainimport "C"import "fmt"//export Sumfunc Sum(a, b int) int { return a + b}//export Testfunc Test() int { var s int for i := 0; i < 1000000; i++ { s += i } return s}func main() { fmt.Println(Test())}其中Sum函数上方的//export Sum是必须的,没有这个注释,不会被识别成为导出的对象。export后面的字符是导出后的名字。import "C"也是必须的。
使用如下指令编译:go build -buildmode=c-shared -o sum.so main.gosum.so是编译后的文件,main.go是要编译的文件。
编译完成后,会在当前目录生成sum.so,和sum.h文件。
这里来看一下sum.h文件,下面截取了一段关键的代码:#ifdef __cplusplusextern "C" {#endifextern GoInt Sum(GoInt p0, GoInt p1);extern GoInt Test();#ifdef __cplusplus}#endif可以看到导出了一个Sum函数和一个Test函数,如果不写//export funcname是不会有这个的。
将sum.so和sum.h放到python的目录下面。然后再来编辑test.py文件,这个文件和sum.so同目录下即可。
test.pyfrom ctypes import CDLLimport ctypesif __name__ == "__main__": test = CDLL("./sum.so").Test test.restype = ctypes.c_int64 print(test())使用CDLL("./sum.so")加载so模块,然后使用.Test导向Test函数,这里必须和sum.h里一致。test.restype = ctypes.c_int64将Test的函数返回类型声明为int64,在这里的机器上,默认的结果使用的返回类型为int32,会得到一个错误的结果。需要显示的声明为int64。
最后使用test()来调用。运行程序,查看结果。
除了上面的定义返回值得类型,还可以定义传入参数的类型,上面的代码中,还定义了一个sum函数。
sum = CDLL("./sum.so").Sum sum.argtypes = [ctypes.c_int32, ctypes.c_int32] sum.restype = ctypes.c_int32 print(sum(1, 2))argtypes将接受一个列表类型,里面是各个参数的类型。
来测试一下调用go语言代码的速度,和用python自己写的速度有什么区别。from ctypes import CDLLimport ctypesimport datetimedef speedTest(): sum = 0 for i in range(1000000): sum += i return sumif __name__ == "__main__": test = CDLL("./sum.so").Test test.restype = ctypes.c_int64 start = datetime.datetime.now() for i in range(10): test() end = datetime.datetime.now() print(end - start) start = datetime.datetime.now() for i in range(10): speedTest() end = datetime.datetime.now() print(end - start) print("done")go的代码就不放出来了,和python一样,也就用for循环计算累加1000000的值。其实这里的测试不太严谨,但是不影响粗略来看测试的结果。可以发现,两者的速度根本就不是一个数量级的。这还仅仅只是时间上的差距,内存上的差距还没有去看。所以如果对项目的耗时非常严格的话,不妨将其中的耗时操作交给go甚至是c语言去实现,能极大提升性能。
一般来说往函数传递参数的时候,不只是需要传递int值,需要穿钉string值。这个时候问题就来了,传递一个string值会怎么样?//export printStringfunc printString(a string) { fmt.Println(a)}来看看头文件:#ifdef __cplusplusextern "C" {#endifextern void printString(GoString p0);#ifdef __cplusplus}#endif可以看到函数的参数类型为GoString,这个类型是c语言没有的。往上翻翻能看到GoString的定义:typedef struct { const char *p; ptrdiff_t n; } _GoString_;是一个结构体,有一个char类型的指针和一个代表长度的整数n。
那么在调用这个函数的时候,就需要传递一个GoString的值进去。python侧需要自己来写这个结构体定义。class GoString(ctypes.Structure): _fields_ = [("p", ctypes.c_char_p), ("n", ctypes.c_longlong)]接下去构建参数传递:msg = GoString(b"hello,world!",len(b"hello,world!"))printString(msg)这里得使用字节流才行,因为是C的char类型指针。最后设置接收类型为定义好的GoString就能调用了。同理,也可以把返回值设置为string,在python里面也可以用这个GoString这个类给读出来。但是直接输出会输出一个内存地址,需要用result.p来输出,这个也是一个字节流。
其他诸如go的map和chan,在导出的时候,都会被标识为void *,读取起来非常麻烦,而slice需要一个结构体来实现。class GoSlice(Structure): _fields_ = [("data", POINTER(c_void_p)), ("len", c_longlong), ("cap", c_longlong)]最后说一下限制,go语言写的结构体,是没有办法导出的,所以结果的交互要用结构体的时候,尽量用json之类的封装一下。
声明:
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
注释:本文来自公众号雷神众测