1. 初衷
最近学用Python,python不愧是为程序员考虑的编程语言,写起来很快很方便,大大节省开发效率。而且,对于小规模程序,运行效率也不错。前两天写了一篇博文《【总结】学用python写程序》,大大地夸奖了python一番。不过这两天,我就受到“诅咒”了。数据规模稍微大一点,python的执行效率的差劲就体现出来了。这两天写的一个程序,尽管在我所知道的范围内,我做了python语言能做的优化,不过程序依然运行了五个小时之久。想把程序改成c++的,不过开发时间较长,而且未来可能还有改动。所以暂罢。
上网上查了查python效率的问题。一方面,网上这方面资料不是很多,例如:我们都知道stl里面set是用红黑树实现的,不过python的set怎么实现的,貌似网上没有。这说明用python的人貌似都不关心效率问题。另一方面,据网上资料说,python运行效率比Java还慢。我作为c++程序员从前很鄙视java的运行效率,原来python还不如java呢!不过java是虚拟机,python是解释器,为什么python更慢呢?原因在于python更加“面向对象”,python的所有类型都是对象,连最普通的整数变量都是对象,都要在运行的时候才能够确定类型、才能够动态创建......这大大加重了运行时的负担,所以运行效率才这么差。对比之下,同样的程序用cython写,仅仅是声明了变量类型,运行效率就会有35%的提升。
我从前用过openmp,见从前的博文《简单尝试windows多线程程序》。感觉openmp是神器一个,既方便写程序,又能利用cpu的多个核心,大大提升运行效率。问题是,python中能够使用openmp么?答案是悲观的。python的默认实现是cpython,也就是用c来做的实现,而c的函数大部分都不是线程安全的,为了利用这些函数实现、同时又为了运行时的线程安全,python做了GIL(Global Interpreter Lock)的限制,也就是说,同一段时间内只有一个线程才能够访问python解释器。不过这也使得python上面的并发特别困难。
不过也不是一点方法都没有,cython现在已经支持了openmp。cython是什么?和python、cpython什么关系?python是脚本语言,cpython是用c来实现的python的解释器,cython是另外一种编程语言,介于python和c之间。实际上cython的设计初衷也是这样,既要利用python快捷的编程速度,又要有C语言的运行效率。cython和python的一个显著区别就是,cython的所有变量都要明确声明变量类型——仅仅这一点,相同的程序,cython的运行效率就要比python的高35%!虽然cython是一种独立的编程语言,不过貌似大家不用他独立的编写程序,而是用它来编写python的c扩展(用c高效实现某些程序,再给python调用)。这几天尝试的,就是在cython上面用openmp,并写成python的c扩展,给python调用。
2. 环境
windows7 + 32bit + vistual studio 2008 + python 2.7.3 + eric4,都是默认安装路径。
3. 安装cython
官网上下载的Cython-0.20.1,从控制台上切到cython的路径,运行setup.py就一路编译安装下去了,没遇到其他问题。
在网上看到,很多人在安装的时候遇到很多问题,基本上都是找不到c++编译器,具体表现是提示找不到一个叫“vs....bat”的文件。解决办法通常是安装mingw(gcc在windows下的版本),然后修改一个.cfg文件,指定用这个编译器来build。
我的安装过程没有遇到问题,看网上的解释,貌似是python2.7的cpython是用vistual studio 2008来编译的,默认找对了编译器,所以没问题了。总之,装上了,没问题。
4. 写pyx文件
pyx文件是python的c扩展文件,代码要符合cython的规范,用什么编辑器写都行。我在eric4上写的,结果它默认用python解释器来进行解释,还提示有bug,“语法错误”。不理会他,本来cython的语法在python里面就不支持。创建TestOMP.pyx文件,并在文件中写代码如下:
第一句引入了cython中的并行处理模块,尤其是prange。我理解,prange就是“python 'range' of parallel version”,就是并行循环。第二句是引入了c语言中的‘printf’函数。整个文件就定义了一个Test函数。看到,每个变量在使用前都要声明类型。在prange中,有参数‘num_threads’来设定并发数量。nogil表示‘no gil(Global Interpreter Lock)’,想要获得并行,这个参数就要设置。在循环过程中,调用了c的库函数printf,来打印每个整数值。
5. 写setup.py文件
上面的pyx文件还仅仅是源代码文件,要想被python调用、要想运行,仅仅写了源代码还是不够的。具体来说,还要转成.c或者.c++的文件,并且再进一步转成.pyd文件。pyd文件才是可以直接使用的文件。为了达到上述目的,就要写一个setup.py脚本,如下:
这个完全是一个python脚本,可以在python解释器下面运行。在控制台下,运行如下命令‘python setup.py build_ext --inplace’,就生成了TestOMP.pyd文件。当然,同时还有一些杂七杂八的文件,如‘build’目录下面的‘lib’文件。这都提示着,这是在windows vistual studio环境下。在linux+gcc环境下,就要生成.so文件了,而且“/openmp”的选项就要写成“-fopenmp”
6. 写TestOMP.py文件
上述两个步骤,相当于把某个python效率瓶颈模块(这之前需要用profile工具来定位)用效率更高的代码写成了python的c扩展形式,接下来,就是要在python代码中调用他们。TestOMP.py就是这个调用的脚本,如下:
这个就很容易了,import并且调用。在控制台下,输入“python TestOMP.py”,运行。
7. 结果
上面是在控制台上的输出的一个片段。能够看到,的确是prange把1000000这一个大循环分成了两个区间:[0, 500000) 和 (500001,1000000],两个循环并行运行,并交替使用控制台IO进行输出。
8. 对应的c++程序
同时写了一个对等的c++程序,如下:
同时,在Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜单里选择Yes。并且从C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.OPENMP 和 C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugOpenMP目录下分别拷贝vcomp90d.dll和vcomp90.dll文件到工程文件当前目录下,或者将上述两个路径设置到环境变量里面。
编译、运行,结果和python上面的一样——不过貌似更快!
完。
转载请注明出处:http://blog.csdn.NET/xceman1997/article/details/26977483