什么,听说3分钟入门Cython??

Python的代码优雅而实用,但是它的速度确实不怎样。Cython企图在保留Python美好的同时,兼顾代码的效率,这里先贴出Cython的两个官方文档,以方便大家对Cython更系统的学习:
英文 http://docs.cython.org/en/latest/
中文 https://moonlet.gitbooks.io/cython-document-zh_cn/content/ch1-basic_tutorial.html


为什么最近会和Cython打上交道,说起来是因为我看了一篇名叫《Cython三分钟入门》的文章,毫无疑问文章写得很不错,但是标题起得实在不好,因为太标题党啦!


跑通整个项目我花了大概两天时间,尽管代码量并不大。接下来我仍然会以文章中的例子来进行说明,结合这两天的经历,从三个环境出发:1. Windows环境;2. Linux环境;3. Cgywin环境,将我遇到的问题一并讲清楚,希望能帮助到和我有相同问题的同学。

Cython的安装

Cython是一个Python库,理所当然的安装手段就是

    -> pip intsall cython

当然对于Cygwin用户,也能够进入Cygwin安装程序中下载Cython包,效果是一样的,步骤可以参考我的另一篇文章《Cygwin,让你拥有Windows下的Linux环境》。

代码效率

由于各主机的配置不一样,你测试出来的时间和我不一样很正常,但是这种性能的上升是必定和我一样的!

Python版本

下面是一段Python实现的代码,作用是计算沿地球表面两点之间的距离,是我们的version1。我们会让它调用50万次,并测定它所需的时间:

# version1.py

import math  

def great_circle(lon1,lat1,lon2,lat2):  
    radius = 3956 #miles  
    x = math.pi/180.0  

    a = (90.0-lat1)*(x)  
    b = (90.0-lat2)*(x)  
    theta = (lon2-lon1)*(x)  
    c = math.acos((math.cos(a)*math.cos(b)) +  
              (math.sin(a)*math.sin(b)*math.cos(theta)))  
    return radius*c  

实现以下代码来调用version1::

# efficiency.py

import timeit

lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #调用50万次

t=timeit.Timer("v1.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
               "import version1 as v1")
print('纯python版本用时:'+str(t.timeit(num))+'sec')

好的,我的电脑花了大约3.3s,有点感人。当然我们的时间可能并不一样,但无一例外都不怎么快。

Python+C版本

一定程度上混搭Python和C数据类型,是我们的version2,但要注意,必须是.pyx文件,因为我们需要将其编译为Python拓展,这一步我会在后面仔细讲。我们同样会调用50万次,并测定它所需的时间:

# version2.pyx

import math

def great_circle(float lon1,float lat1,float lon2,float lat2):
    cdef float radius=3956.0
    cdef float pi=3.14159265
    cdef float x=pi/180.0
    cdef float a,b,theta,c

    a=(90.0-lat1)*(x)
    b=(90.0-lat2)*(x)
    theta=(lon2-lon1)*(x)
    c=math.acos((math.cos(a)*math.cos(b) +
            math.sin(a)*math.sin(b)*math.cos(theta)))
    return c*radius  

实现以下代码来调用version2:

# efficiency.py

import timeit

lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #调用50万次

t=timeit.Timer("v2.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
               "import version2 as v2")
print('python+c版本用时:'+str(t.timeit(num))+'sec')

我的电脑花了大约2.2s,与3.3s相比是不是快了不少。相信在你的电脑上也能够看到这么明显的变化。

C版本(Python调用)

我们大致分析一下,version2的瓶颈是在哪里!我们调用的是python的math模块,是不是这里大大地限制了性能?现在我们使用C标准库替代之:

# version3.pyx

cdef extern from "math.h":
    float cosf(float theta)
    float sinf(float theta)
    float acosf(float theta)

def great_circle(float lon1,float lat1,float lon2,float lat2):
    cdef float radius=3956.0
    cdef float pi=3.14159265
    cdef float x=pi/180.0
    cdef float a,b,theta,c

    a=(90-lat1)/(x)
    b=(90-lat2)/(x)
    theta=(lon2-lon1)*(x)
    c=acosf((cosf(a)*cosf(b))+(sinf(a)*sinf(b)*cosf(theta)))

    return radius*c

实现以下代码来调用version3:

# efficiency.py

import timeit

lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826
num=500000 #调用50万次

t=timeit.Timer("v3.great_circle(%f,%f,%f,%f)"%(lon1,lat1,lon2,lat2),
               "import version3 as v3")
print('纯c版本(Python函数调用)用时:'+str(t.timeit(num))+'sec')

0.6s,相当惊人!!这才是我们追求的速度不是吗?

C版本(C调用)

观察上面的代码,容易发现调用50万次这个是循环使用Python实现的。我们知道循环是一个相当耗时的操作,那么如果我们把这个循环放到C代码里,是否能更进一步地提升性能:

# version4.pyx

cdef extern from "math.h":
    float cosf(float theta)
    float sinf(float theta)
    float acosf(float theta)

cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):
    cdef float radius=3956.0
    cdef float pi=3.14159265
    cdef float x=pi/180.0
    cdef float a,b,theta,c

    a=(90-lat1)*(x)
    b=(90-lat2)*(x)
    theta=(lon2-lon1)*(x)
    c=acosf((cosf(a)*cosf(b))+(sinf(a)*sinf(b)*cosf(theta)))

    return radius*c

def great_circle(float lon1,float lat1,float lon2,float lat2,int num):
    cdef int i
    cdef float x
    for i from 0<=i<num:
        x=_great_circle(lon1,lat1,lon2,lat2)
    return x

实现以下代码来调用version4:

# efficiency.py

import timeit

lon1,lat1,lon2,lat2=-72.345,34.323,-61.823,54.826

t=timeit.Timer("v4.great_circle(%f,%f,%f,%f,%i)"%(lon1,lat1,lon2,lat2,num),
           "import version4 as v4")
print('纯c版本(C函数调用)用时:'+str(t.timeit(1))+'sec')

惊喜地发现,我们把性能提升到了0.12s,速度提高了将近30倍,very amazing。

C代码实现

究竟Cython中最快的版本version4和C实现的代码,在性能上相比会有多大的差距?我这里准备了一段C代码:

#include <math.h>
#include <stdio.h>
#include <time.h>
#define NUM 500000

//version5.c

float great_circle(float lon1,float lat1,float lon2,float lat2){
    float radius=3956.0;
    float pi=3.14159265;
    float x=pi/180.0;
    float a,b,theta,c;

    a=(90.0-lat1)*(x);
    b=(90.0-lat2)*(x);
    theta=(lon2-lon1)*(x);
    c=acos((cos(a)*cos(b))+(sin(a)*sin(b)*cos(theta)));
    return radius*c;
}

int main(){
    int i;
    float x;
    clock_t start, finish;
    double Total_time;
    start = clock();
    for(i=0;i<=NUM;i++)
        x=great_circle(-72.345,34.323,-61.823,54.826);
    finish = clock();
    Total_time = (double)(finish-start) / CLOCKS_PER_SEC;
    printf("%f sec",Total_time);
    printf("\n");
    printf("%f",x);
}

当然,如果你有C语言的集成环境,直接运行就能可以得到结果!我们知道Linux系统的gcc组件能够编译C代码,为了方便,我们直接在Linux系统下编译运行,这里我提供两种方式:

方式一:
->Linux环境下:gcc -lm -octest version5.c
   当前路径生成ctest.exe
->time ./ctest
   测试该模块运行所需时间

对于使用Cygwin来完成这条命令的用户,可能会遇到下面的麻烦,博主也遇到了
<center><font color="red">错误:</font>pyconfig.h No such file or directory</center>
通过网上查阅相关资料,发现这是由于某些组件的缺失造成的,它们可能是下面这些组件:

  1. python-devel(对应python2.x)/python3-devel(对应python3.x)
  2. libxml2-dev
  3. libxslt-dev
    一般来说,主要是python-devel的原因。总之,看情况吧。

方式二:
->Linux环境下:gcc -o version5 version5.c
   当前路径生成version5.exe
->./version5
   测试该模块运行所需时间



可能由于测试的方式不同,这里用时反而还要长那么一点。但总的来说,Cython能够有效地改善性能。当然,大多数情况下,Python的性能是足够好的,一旦循环、数字运算和Python函数调用上去了,性能就会相应地下降,在这种情况下,我建议你们使用Cython进行优化。



Cython编译

我们之前安装的Cython就是用在这里的。

  • 对于Windows系统,编译以下代码能够得到:.pyx->.pyd
  • 对于Linux系统,编译一下代码能够得到:.pyx->.c->.o->.dll


    这是对应的,因为Windows系统能够使用的Python拓展是.pyd文件,而Linux系统能够使用的Python拓展是.o或.dll文件。但是我们完全不用担心,你的系统总是有选择地去做适合它自己的事情,我们只需要顺水推舟。
#setup.py

# Run as:
#    python setup.py build   编译
#    python setup.py install 安装(效果同pip install xxx)

from distutils.core import setup
from Cython.Build import cythonize

#cythonize:编译源代码为C或C++,返回一个distutils Extension对象列表
setup(ext_modules=cythonize('XXXXX.pyx'))

我们在前文提到的.pyx文件并不能直接作为Python拓展,我们需要编写setup.py来帮助我们获得Python拓展。在'XXXXX.pyx'中填入.pyx文件的路径,然后我们在shell环境下(setup.py的目录下)执行以下命令:
python setup.py build
一般来说,我们能在当前目录下看到一个名为build的文件夹,我们需要的Python拓展就在里面!注意执行该命令的python版本要对应。

执行以下命令:
python setup.py install
安装该模块到site-package文件夹下。

Cython编译中的问题

我之所以无法像文章所说的那样三分钟入门,就是因为这些乱七八糟的问题,这里我汇总一下我自己遇到的问题,希望后来的人不要像我一样走弯路。原生Linux环境问题不大,会出现问题主要就是Windows系统和Cygwin这种伪Linux环境,我就从这两个环境出发:

Windows

Unable to find vcvarsall.bat####

这时候的我们,一般直接就把这个错误贴到百度或者google上,那么我们会看到一堆关于Windows下Cython安装的教程。
注意,不要按网上说的,安装MinGW,然后在"..python安装路径...\Lib\distutils"下新建一个distutils.cfg文件,在这文件里面制订编译器为mingw32
如:
[build]
compiler=mingw32
一方面,我的电脑上已经安装了Cygwin,为什么非得另外安装一个MinGW,另一方面MinGW编译出来的东西,安装上了也有不好使的时候,甚至会无法编译;即使编译通过,安装上了,你安装的Python标准库不是由mingw编译的,你的拓展包却是mingw编译的,很难说能够完全兼容或者质量跟得上。(引用自参考文献2)
但是参考文献2中提到的方法却不是很管用,可能不是很适合我的情况吧。那么要如何解决这个问题?这主要是因为涉及到了比较底层的问题,由于底层上对python支持的不足造成的。参考文献3提出的方法完美地解决了我的问题:
打开Visual Studio的安装程序进入到下面的界面,选择下面这些组件安装。


等待安装完成,再次执行命令发现能够成功编译!大成功~~

Cygwin

打开Cygwin,执行以下命令
python setup.py build
黑色的界面上弹出刺眼的红色,提示你出错了

致命错误:Python.h:没有那个文件或目录####

其实说白了,Cygwin下报错,大多数情况就是想告诉你,你丫的有哪些哪些组件没下载呀呀!!
是的,只要我们安装python-devel(python2.x)/python3-devel(python3.x),便发现问题迎刃而解啦!




这几天收获很大,巨开心!




项目链接: https://github.com/kingboung/Miniproject/tree/master/Cython_accidence

转载请告知!!博主个人网站:http://www.kingboung.me
文章有不完善的地方,请留言告知!谢谢我的朋友们。
参考文献:


《Cython三分钟入门》        赖勇浩 译
《彻底解决 error: Unable to find vcvarsall.bat》        天才白痴书馆
    知乎之提问        知乎用户 答

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容