加餐 | 带你上手SWIG:一份清晰好用的SWIG编程实践指南
下载APP
关闭
渠道合作
推荐作者
加餐 | 带你上手SWIG:一份清晰好用的SWIG编程实践指南
2019-08-16 卢誉声 来自北京
《Python核心技术与实战》
课程介绍
讲述:冯永吉
时长14:16大小13.07M
你好,我是卢誉声,Autodesk 数据平台和计算平台资深软件工程师,也是《移动平台深度神经网络实战》和《分布式实时处理系统:原理架构与实现》的作者,主要从事 C/C++、JavaScript 开发工作和平台架构方面的研发工作,对 SWIG 也有比较深的研究。很高兴受极客时间邀请来做本次分享,今天,我们就来聊一聊 SWIG 这个话题。
我们都知道,Python 是一门易于上手并实验友好的胶水语言。现在有很多机器学习开发或研究人员,都选择 Python 作为主力编程语言;流行的机器学习框架也都会提供 Python 语言的支持作为调用接口和工具。因此,相较于学习成本更高的 C++ 来说,把 Python 作为进入机器学习世界的首选编程语言,就再合适不过了。
不过,像 TensorFlow 或 PyTorch 这样的机器学习框架的核心,是使用 Python 编写的吗?
显然不是。这里面的原因比较多,但最为显著的一个原因就是“性能”。通过 C++ 编写的机器学习框架内核,加上编译器的优化能力,为系统提供了接近于机器码执行的效率。这种得天独厚的优势,让 C++ 在机器学习的核心领域站稳了脚跟。我们前面所说的 TensorFlow 和 PyTorch 的核心,便都是使用 C/C++ 开发的。其中,TensorFlow 的内核,就是由高度优化的 C++ 代码和 CUDA 编写而成。
因此,我们可以理解为,TensorFlow 通过 Python 来描述模型,而实际的运算则是由高性能 C++ 代码执行的。而且,在绝大多数情况下,不同操作之间传递的数据,并不会拷贝回 Python 代码的执行空间。机器学习框架,正是通过这样的方式确保了计算性能,同时兼顾了对框架易用性方面的考虑。
因此,当 Python 和 C++ 结合使用的时候,Python 本身的性能瓶颈就不那么重要了。它足够胜任我们给它的任务就可以了,至于对计算有更高要求的任务,就交给 C++ 来做吧!
今天,我们就来讨论下,如何通过 SWIG 对 C++ 程序进行 Python 封装。我会先带你编写一段 Python 脚本,来执行一个简单的机器学习任务;接着,尝试将计算密集的部分改写成 C++ 程序,再通过 SWIG 对其进行封装。最后的结果就是,Python 把计算密集的任务委托给 C++ 执行。
我们会对性能做一个简单比较,并在这个过程中,讲解使用 SWIG 的方法。同时,在今天这节课的最后,我会为你提供一个学习路径,作为日后提高的参考。
明确了今天的学习目的,也就是使用 SWIG 来实现 Python 对 C++ 代码的调用,那么,我们今天的内容,其实可以看成一份关于 SWIG 的编程实践指南。学习这份指南之前,我们先来简单了解一下 SWIG。
SWIG 是什么?
SWIG,是一款能够连接 C/C++ 与多种高级编程语言(我们在这里特别强调 Python)的软件开发工具。SWIG 支持多种不同类型的目标语言,这其中,支持的常见脚本语言包括 JavaScript、Perl、PHP、Tcl、Ruby 和 Python 等,支持的高级编程语言则包括 C#、D、Go 语言、Java(包括对 Android 的支持)、Lua、OCaml、Octave、Scilab 和 R。
我们通常使用 SWIG 来创建高级解释或编译型的编程环境和接口,它也常被用来当作 C/C++ 编写原型的测试工具。一个典型的应用场景,便是解析和创建 C/C++ 接口,生成胶水代码供像 Python 这样的高级编程语言调用。近期发布的 4.0.0 版本,更是带来了对 C++ 的显著改进和支持,这其中包括(不局限于)下面几点。
针对 C#、Java 和 Ruby 而改进的 STL 包装器。
针对 Java、Python 和 Ruby,增加 C++11 标准下的 STL 容器的支持。
改进了对 C++11 和 C++14 代码的支持。
修正了 C++ 中对智能指针 shared_ptr 的一系列 bug 修复。
一系列针对 C 预处理器的极端 case 修复。
一系列针对成员函数指针问题的修复。
低支持的 Python 版本为 2.7、3.2-3.7。
使用 Python 实现 PCA 算法
借助于 SWIG,我们可以简单地实现用 Python 调用 C/C++ 库,甚至可以用 Python 继承和使用 C++ 类。接下来,我们先来看一个你十分熟悉的使用 Python 编写的 PCA(Principal Component Analysis,主成分分析)算法。
因为我们今天的目标不是讲解 PCA 算法,所以如果你对这个算法还不是很熟悉,也没有关系,我会直接给出具体的代码,我们把焦点放在如何使用 SWIG 上就可以了。下面,我先给出代码清单 1。
代码清单 1,基于 Python 编写的 PCA 算法 testPCAPurePython.py :
现在,我们保存这段编写好的代码,并通过下面的命令来执行:
准备 SWIG
这样,我们已经获得了一些进展——使用 Python 编写了一个 PCA 算法,并得到了一些结果。接下来,我们看一下如何开始 SWIG 的开发工作。我会先从编译相关组件开始,再介绍一个简单使用的例子,为后续内容做准备。
一切就绪后,我们就来编写一个简单的例子吧。这个例子同样来源于 SWIG 网站(http://swig.org/tutorial.html)。我们先来创建一个简单的 c 文件,你可以通过你习惯使用的文本编辑器(比如 vi),创建一个名为example.c的文件,并编写代码。代码内容我放在了代码清单 2 中。
代码清单 2,example.c:
接下来,我们编写一个名为example.i的接口定义文件,和稍后用作测试的 Python 脚本,内容如代码清单 3 和代码清单 4 所示。
代码清单 3,example.i:
我来解释下清单 3 这段代码。第 1 行,我们定义了模块的名称为 example。第 2-8 行,我们直接指定了example.c中的函数定义,也可以定义一个example.h头文件,并将这些定义加入其中;然后,在 %{ … %}结构体中包含example.h,来实现相同的功能。第10-13行,则是定义了导出的接口,以便你在 Python 中直接调用这些接口。
代码清单 4,testExample.py:
好了, 到现在为止,我们已经准备就绪了。现在,我们来执行下面的代码,创建目标文件和最后链接的文件吧:
其实,从代码清单 4 中你也能够看到,通过导入 example,我们可以直接在 Python 脚本中,调用使用 C 实现的函数接口,并获得返回值。
通过 SWIG 封装基于 C++ 编写的 Python 模块
到这一步,我们已经准备好了一份使用 C++ 编写的 PCA 算法,接下来,我们就要对其进行一个简单的封装。由于 C++ 缺少线性代数的官方支持,因此,为了简化线性代数运算,我这里用了一个第三方库 Armadillo。在 Ubuntu 下,它可以使用apt-get install libarmadillo-dev安装支持。
另外,还是要再三说明一下,我们今天这节课的重点并不是讲解 PCA 算法本身,所以希望你不要困于此处,而错过了真正的使用方法。当然,为了完整性考虑,我还是会对代码做出最基本的解释。
封装正式开始。我们先来编写一个名为pca.h的头文件定义,内容我放在了代码清单 5 中。
代码清单 5,pca.h:
接着,我们再来编写具体实现pca.cpp,也就是代码清单 6 的内容。
代码清单 6,pca.cpp:
这里要注意了,代码清单 6 中用到了utils.h这个文件,它是对部分矩阵和数学计算的封装,内容我放在了代码清单 7 中。
代码清单 7,utils.h:
至于具体的实现代码,我放在了在代码清单 8utils.cpp中。
代码清单 8,utils.cpp:
最后,我们来编写pca.i接口文件,也就是代码清单 9 的内容。
代码清单 9,pca.i:
这里需要注意的是,我们在 C++ 代码中使用了熟悉的顺序容器std::vector,但由于模板类比较特殊,我们需要用%template声明一下。
一切就绪后,我们执行下面的命令行,生成_pca.so库供 Python 使用:
接着,我们使用 Python 脚本,导入我们创建好的 so 动态库;然后,调用相应的类的函数。这部分内容,我写在了代码清单 10 中。
代码清单 10,testPCA.py:
最后,我们分别对纯 Python 实现的代码,和使用 SWIG 封装的版本来进行测试,各自都执行 1,000,000 次,然后对比执行时间。我用一张图表示了我的机器上得到的结果,你可以对比看看。
虽然这样粗略的比较并不够严谨,比如我们没有认真考虑 SWIG 接口类型转换的耗时,也没有考虑在不同编程语言下实现算法的逻辑等等。但是,通过这个粗略的结果,你仍然可以看出执行类似运算时,两者性能的巨大差异。
SWIG C++ 常用工具
到这里,你应该已经可以开始动手操作了,把上面的代码清单当作你的工具进行实践。不过,SWIG 本身非常丰富,所以这里我也再给你总结介绍几个常用的工具。
1. 全局变量
在 Python 中,我们可以通过 cvar,来访问 C++ 代码中定义的全局变量。
比如说,我们在头文件 sample.h中定义了一个全局变量,并在sample.i中对其进行引用,也就是代码清单 11 和 12 的内容。
代码清单 11,sample.h:
代码清单 12,sample.i:
这样,我们就可以直接在 Python 脚本中,通过 cvar 来访问对应的全局变量,如代码清单 13 所示,输出结果为 100。
代码清单 13,sample.py:
2. 常量
我们可以在接口定义文件中,使用 %constant来设定常量,如代码清单 14 所示。
代码清单 14,sample.i:
3.Enumeration
我们可以在接口文件中,使用 enum 关键字来定义 enum。
4. 指针和引用
在 C++ 世界中,指针是永远也绕不开的一个概念。它无处不在,我们也无时无刻不需要使用它。因此,在这里,我认为很有必要介绍一下,如何对 C++ 中的指针和引用进行操作。
SWIG 对指针有着较为不错的支持,对智能指针也有一定的支持,而且在近期的更新日志中,我发现它对智能指针的支持一直在更新。下面的代码清单 15 和 16,就展示了针对指针和引用的使用方法。
代码清单 15,sample.h:
代码清单 16,sample.py:
5. 字符串
我们在工业级代码中,时常使用std::string。而在 SWIG 的环境下,使用标准库中的字符串,需要你在接口文件中声明%include “std_stirng.i”,来确保实现 C++ std::string到 Python str的自动转换。具体内容我放在了代码清单 17 中。
代码清单 17,sample.i:
6. 向量
std::vector是 STL 中最常见也是使用最频繁的顺序容器,模板类比较特殊,因此,它的使用也比字符串稍微复杂一些,需要使用%template进行声明。详细内容我放在了代码清单 18 中。
代码清单 18,sample.i:
7. 映射
std::map 同样是 STL 中最常见也是使用最频繁的容器。同样的,它的模板类也比较特殊,需要使用%template进行声明,详细内容可见代码清单 19。
代码清单 19,sample.i:
学习路径
到此,SWIG 入门这个小目标,我们就已经实现了。今天内容可以当作一份 SWIG 的编程实践指南,我给你提供了 19 个代码清单,利用它们,你就可以上手操作了。当然,如果在这方面你还想继续精进,该怎么办呢?别着急,今天这节课的最后,我再和你分享下,我觉得比较高效的一条 SWIG 学习路径。
首先,任何技术的学习不要脱离官方文档。SWIG 网站上提供了难以置信的详尽文档,通过文档掌握 SWIG 的用法,显然是最好的一个途径。
其次,要深入 SWIG,对 C++ 有一个较为全面的掌握,就显得至关重要了。对于高性能计算来说,C++ 总是绕不开的一个主题,特别是对内存管理、指针和虚函数的应用,需要你实际上手编写 C++ 代码后,才能逐渐掌握。退一步讲,即便你只是为了封装其他 C++ 库供 Python 调用,也需要对 C++ 有一个基本了解,以便未来遇到编译或链接错误时,可以找到方向来解决问题。
最后,我再罗列一些学习素材,供你进一步学习参考。
第一便是 SWIG 文档。
第二是《C++ Primer》这本书。作为 C++ 领域的经典书籍,这本书对你全面了解 C++ 有极大帮助。
第三则是《高级 C/C++ 编译技术》这本书。这本书的内容更为进阶,你可以把它作为学习 C++ 的提高和了解。
好了,今天的内容就到此结束了。关于 SWIG,你有哪些收获,或者还有哪些问题,都欢迎你留言和我分享讨论。也欢迎你把这篇文章分享给你的同事、朋友,我们一起学习和进步。
分享给需要的人,Ta购买本课程,你将得18元
生成海报并分享
赞 12
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
42 | 细数技术研发的注意事项
下一篇
43 | Q&A:聊一聊职业发展和选择
精选留言(8)
- gutentag2019-08-16对于单文件而言,用SWIG还是boost.python/py++感觉都好理解和实践,请问对于依赖关系复杂的大型C++项目(比如OpenCV, OpenSceneGraph之类的)的python binding有没有比较完整的最佳实践呢? C++编译的动态库python无法直接调用,C++项目的python binding本身等价于把本身编译时用到的所有的头文件中需要暴露的接口都extern成C的呢?对于头文件的相互各种include一般是人工处理还是SWIG本身可以解决呢?除了头文件暴露以外,还有别的工作吗? 任何C项目直接生成的动态链接库python都能直接import吗?请问有例外吗? 谢谢展开共 1 条评论8
- 许童童2019-08-16极客时间的C++课程快来了,期待一下,补一补我的C++。共 1 条评论5
- 安排2019-08-16类似于jni啊4
- Ethan2019-08-16c++大法3
- Felix2020-03-17老师,实际使用中遇到个问题,想请教下您: 假如有这么一个C++函数:ErrCode GetTpError(std::string& errMsg); 用于获取错误信息,想要在python中调用,利用swig编译OK,但调用后没有得到 errMsg字符串,原因是python的字符串类型是immutable,不知道我这样理解对吗?还有这个函数要怎么转换,才能在python中调用呢?2
- -.----..2019-08-20感觉SWIG更灵活,比ctypes和py4j更方便,但是Python调用.so文件好像很挑gcc版本,不同版本gcc编译的.so文件,Python调用时有时候会报共 1 条评论2
- 好好先生2020-03-29加油!1
- 栾~龟虽寿!2019-08-18如何看python源代码,比如list.sort的实现1