百度首页 | 
百度搜藏
快照
(百度和http://blog.absolute2.cn/posts/12的作者无关,不对其内容负责。百度快照谨为网络故障时之索引,不代表被搜索网站的即时页面。)

Python优化:用C来扩展 | Absolute 2


Python优化:用C来扩展

原作:kortis.to
翻译:nasi

恩,你要用你最喜欢的语言来开发你的项目,当然这个语言就是Python咯 :) 但是同时,你还要面对计算量非常大的问题。但是,用Python来作大规模的计算通常并不太适合,因为它是一门解释性的语言。听起来是不是满熟悉的?

优化,优化…

当然,优化你的python代码是一个方法。这个在这里并不深入讨论,这里有优化python代码不错的资料。另外还有一个方法,就是使用更加有效率的算法。这个是让你的程序变快的一个主要方法。

但是,如果你已经优化了你的代码了呢?而且你也使用了你所知的运行最快的算法,但是程序依然像乌龟一样慢呢?恩,还有一个方法(除了升级你的电脑):用C(或者C++)来扩展你的程序。用Python舒舒服服的来写其他的部分,而最耗CPU的部分就用C来写。

案例学习: 计算素数

比如说,你有一个问题,需要计算出前n个素数。(为什么?恩,这就取决于你了 :) )然后,你用python来写,写出来大概这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
 
def isprime1(input):
    if input < 1:
	return 0
 
    n = input - 1
 
    while n > 1:
	if input%n == 0:
	    return 0
	n = n - 1
 
    return 1
 
def main():
    i = 0
    l = 0 
 
    max = int(sys.argv[1])
 
    while 1:
 
	if isprime1(i):
	    l = l + 1
 
	i = i + 1
	if l == max:
	    break
 
    print max, "primes calculated"
 
if __name__ == "__main__":
    main()

这个isprime1函数用来计算一个数是不是素数。我猜这应该不是“最优算法”,不过这不重要。得益于Python的高可读性,这个代码不需要任何注释了,对吧?:)

这个程序运行起来很漂亮,但是,你应该注意到,随着素数数量的增加,程序会开始变得非常慢。通过我们的观察,同样的C程序大概要快上30倍!哇,用Python就要等上30倍吗?这是C的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
 
int isprime1(int input)
{
	int n;
 
	if (input < 1) {
		return 0;
	}
	n = input - 1;
 
	while (n > 1){
		if (input%n == 0) return 0;
		n--;
	}
	return 1;
}
 
int main(int argc, char *argv[]){
	int i=0, l=0;
	int max;
 
	max = atoi(argv[1]);
 
	while (1) {
		if (isprime1(i)==1) {
			l++;
		}
		i++;
		if (l==max){
			break;
		}
	}
 
	printf("%i primes calculated\n",max);
}

看上去和Python版本的同样漂亮。

解决方案

那么,现在该怎么办?正如我一开始说的那样,我们把素数检测的部分用C来写。直觉上来说,它应该管用,但是不会非常管用。再猜猜…

比较

让我们来看一下我做的计时… 这有个图来说明计算前n个素数所用的时间。

Prime time calculation
点击放大

正如你看到的,用纯Python来写的代码比起C的版本很快就变的越来越慢了。但是,令人惊讶的是,用Python+C的版本竟然几乎和纯C版本的程序一样快。太好了。

下面再来深入看看纯C版本和Python+C版本的程序之间的对比。

C / Python + C compare
点击放大

随着n的增大,C版本的程序和Python+C版本的程序之间的差距越来越小。真是没有比这再好的消息了! :)

扩展

那么,什么是扩展?怎么去做扩展?从根本上说,扩展无非就是用C来写一个Python的module罢了。更加深入的细节和过程的文档都在Python文档中有叙述,当然,这里我将给你一个小示例来给你开开胃。

第一步

确定你要用C来写什么。在我们这个示例中,很明显素数检测就是我们要用C来写、用来提速的东东。

我们已经写过这个函数了,现在要做的就是要让它和Python“兼容”而已。我们把新的源代码命名为“prmodule.c”。(我在这里展示的示例除了计算素数以外,其实是对Python文档的模仿,所以想要更多的信息请参考Python文档)

在prmodule.c中的isprime函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <Python.h>
 
static PyObject *pr_isprime(PyObject *self, PyObject *args){
	int n, input;
 
	if (!PyArg_ParseTuple(args, "i", &input))
	  return NULL;
 
	if (input < 1) {
		return Py_BuildValue("i", 0);
	}
 
	n = input - 1;
 
	while (n > 1){
		if (input%n == 0) return Py_BuildValue("i", 0);
		n--;
	}
 
	return Py_BuildValue("i", 1);
}

这个函数需要返回一个PyObject指针,同时还需要将PyObject指针作为参数。这个就是我们从python的调用中获得的真实参数。PyArg_ParseTuple()用来解析从python传递过来的参数。我们从参数出解析出一个整数,通过这个整数来告诉我们需要检查多少个素数。当返回一个值的时候,我们通过python对象传递回去。

简单直接吧,你一定这么想。但是,prmodule.c还远远没结束呢。我们需要告知我们的module中有哪些方法是可用的。最后,还要为module做一个初始化。

所以,我们添加下面这些到prmodule.c的源文件中:

22
23
24
25
26
27
28
29
static PyMethodDef PrMethods[] = {
	{"isprime", pr_isprime, METH_VARARGS, "Check if prime."},
	{NULL, NULL, 0, NULL}
};
 
void initpr(void){
	(void) Py_InitModule("pr", PrMethods);
}

第二步

接下来,我们需要编译一下我们的module。这里有一个非常好的distutils包正是干这个的。可能你已经从很多python的库中看到了这些名叫setup.py的文件。这就是我们要做的。(我们没有在任何地方实际的安装任何东西,仅仅是编译而已)

让我们写一下setup.py:

1
2
3
4
from distutils.core import setup, Extension
 
module = Extension('pr', sources = ['prmodule.c'])
setup(name = 'Pr test', version = '1.0', ext_modules = [module])

然后运行“python setup.py build”,这样就会编译这个module。就是这样!编译的结果将会在build/lib.linux-i686-2.1(或者类似的)目录中,命名是“pr.so”。你可以把它复制到你的源代码所在的目录,然后和其他module一样的使用它,比如:

comet:~/prog/python/prime$ python
Python 2.1.3 (#1, Apr 20 2002, 10:14:34) 
[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
Type "copyright", "credits" or "license" for more information.
>>> import pr
>>> pr.isprime(17)
1
>>>

One Response to “Python优化:用C来扩展”

  1. 事实上,现在可以借助SWIG来自动为已有的C语言代码生成封装代码以方便编译生成PYTHON模块。还可以使用BOOST中PYTHON库用C++来编写PYTHON的扩展模块(文档中说只支持PYTHON2.2)。PyQT项目中也有一个类似的工具,只是名字忘了。

Leave a Reply