原标题:30倍!使用Cython加速Python代码

作者:George Seif、Thomas Wolf、Lukas Frei

编译:1+1=6 | 公众号海外部

前言

你可能经常会一次又一次地听到关于Python的抱怨,Python跑起来太慢了!

与许多其他编程语言相比,Python的确很慢。

有几种不同的方法可以使代码提速:

如果你的代码是纯Python。如果你有一个很大的for循环,你只能使用它,而不能放入矩阵中,因为数据必须按顺序处理,那该怎么办?有没有办法加快Python本身的速度?

来吧,看看Cython!

文末下载Cython相关书籍

什么是Cython?

Cython的核心是Python和C / C++之间的一个中间步骤。它允许N你编写纯Python代码,只需要做一些小修改,然后将其直接翻译成C代码。

Cython 语言是 Python 的一个超集,它包含有两种类型的对象:

Python 对象就是我们在常规 Python 中使用到的那些对象,诸如数值、字符串、列表和类实例等等。

Cython C 对象就是那些 C 和 C++ 对象,诸如双精度、整型、浮点数、结构和向量,它们能够由 Cython 在超级高效的低级语言代码中进行编译。

你对Python代码所做的唯一调整就是向每个变量添加类型信息。通常,我们可以像这样在Python中声明一个变量:

x = 0.5

使用Cython,我们为该变量添加一个类型:

cdef float x = 0.5

这告诉Cython,变量是浮点数,就像我们在C中所做的一样。对于纯Python,变量的类型是动态确定的。Cython中类型的显式声明使其转为C代码成为可能,因为显式类型声明需要+。

有很多办法来测试、编译和发布 Cython 代码。Cython 甚至可以像 Python 一样直接用于 Jupyter Notebook 中。有很多办法来测试、编译和发布 Cython 代码。Cython 甚至可以像 Python 一样直接用于 Jupyter Notebook 中。

安装Cython只需要一行pip:

pip install cython

使用Cython需要安装C语言编译器,因此,安装过程会根据你当前的操作系统而有所不同。对于Linux,通常使用GNU C编译器(gncc)。对于Mac OS,你可以下载Xcode以获取gncc。而Windows 桌面系统下安装C编译器会更复杂。

使用 %load_ext Cython 指令在 Jupyter notebook 中加载 Cython 扩展。

然后通过指令 %%cython,我们就可以像 Python 一样在 Jupyter notebook 中使用 Cython。

如果在执行 Cython 代码的时候遇到了编译错误,请检查 Jupyter 终端的完整输出信息。

大多数情况下可能都是因为在 %%cython 之后遗漏了 -+ 标签(比如当你使用 spaCy Cython 接口时)。如果编译器报出了关于 Numpy 的错误,那就是遗漏了 import numpy。

如果你要在在IPython中使用Cython:

首先介绍一下IPython Magic命令。Magic命令以百分号开头,通常有2种类型:

单行Magic由单个'%'表示,并且仅在一行输入上操作。

单元格Magic用两个'%'表示,并在多行输入上操作。

首先运行下列语句引入Cython:

%load_ext Cython

然后,当运行Cython代码时,我们需要加入以下Cython 代码:

%%cython

然后就可以愉快地使用Cython了。

Cython中的类型

使用Cython时,变量和函数有两组不同的类型。

对于变量,我们有:

cdef int a, b, c

cdef char *s

cdef float x = 0.5 (single precision)

cdef double x = 63.4 (double precision)

cdef list names

cdef dict goals_for_each_play

cdef object card_deck

注意所有这些类型都来自C / C++ !

def - 常规Python函数,仅从Python调用。

cdef - 仅限Cython函数,接受Python对象或C值作为参数,并且可以返回Python对象或C值,cdef函数不能直接在Python中调用。

cpdef - 接受Python对象或C值作为参数,并且可以返回Python对象或C值。

我们可以方便的向C代码传递和返回结果,Cython会自动为我们做相应的类型转化。

了解了Cython类型之后,我们就可以直接实现加速了!

如何使用Cython加速代码

我们要做的第一件事是设置Python代码基准:用于计算数字阶乘的for循环。原始Python代码如下:

deftest(x):

y = 1

fori inrange(x+1):

y *= i

returny

Cython的实现过程看起来非常相似。首先,确保Cython代码文件具有 .pyx 扩展名。这些文件将被 Cython 编译器编译成 C 或 C++ 文件,再进一步地被 C 编译器编译成字节码文件。

你也可以使用 pyximport 将一个 .pyx 文件直接加载到 Python 程序中:

importpyximport; pyximport.install

importmy_cython_module

你也可以将自己的 Cython 代码作为 Python 包构建,然后像正常的 Python 包一样将其导入或者发布。不过这种做法需要花费更多的时间,特别是你需要让 Cython 包能够在所有的平台上运行。如果你需要一个参考样例,不妨看看 spaCy 的安装脚本:

https://github.com/explosion/spaCy/blob/master/setup.py?source=post_page---------------------------

最终 Python 解释器将能够调用这些字节码文件。对代码本身的惟一更改是,我们已经声明了每个变量和函数的类型。

cpdef int test(int x):

cdef int y = 1

cdef int i

fori inrange(x+1):

y *= i

returny

注意函数有一个cpdef来确保我们可以从Python调用它。另外看看我们的循环变量i是如何具有类型的。你需要为函数中的所有变量设置类型,以便C编译器知道使用哪种类型!

接下来,创建一个setup.py文件,该文件将Cython代码编译为C代码:

fromdistutils.core importsetup

fromCython.Build importcythonize

setup(ext_modules = cythonize('run_cython.pyx'))

并执行编译:

python setup.py build_ext --inplace

Boom!我们的C代码已经编译好,可以使用了!

你将看到,在Cython代码所在的文件夹中,拥有运行C代码所需的所有文件,包括run_cython.c文件。如果你感兴趣,可以查看一下Cython生成的C代码!

现在我们准备测试新的C代码!查看下面的代码,它将执行一个速度测试,将原始Python代码与Cython代码进行比较。

现在我们准备测试我们新的超快速C代码了!查看下面的代码,它执行速度测试以将原始Python代码与Cython代码进行比较。

importrun_python

importrun_cython

importtime

number = 10

start = time.time

run_python.test(number)

end = time.time

py_time = end - start

print("Python time = {}".format(py_time))

start = time.time

run_cython.test(number)

end = time.time

cy_time = end - start

print("Cython time = {}".format(cy_time))

print("Speedup = {}".format(py_time / cy_time))

Cython可以让你在几乎所有原始Python代码上获得良好的加速,而不需要太多额外的工作。需要注意的关键是,循环次数越多,处理的数据越多,Cython可以提供的帮助就越多。

查看下表,该表显示了Cython为不同的阶乘值提供的速度我们使用Cython获得了超过36倍的加速!

Cython在NLP中的加速应用

当我们在操作字符串时,要如何在 Cython 中设计一个更加高效的循环呢?spaCy是个不错的选择!

spaCy 中所有的unicode字符串(the text of a token, its lower case text, its lemma form, POS tag label, parse tree dependency label, Named-Entity tags…)都被存储在一个称为StringStore的数据结构中,它通过一个64位哈希码进行索引,例如C类型的 uint64_t。

StringStore对象实现了Python unicode字符串与 64 位哈希码之前的查找映射。

它可以spaCy的任何地方和任意对象进行访问,例如npl.vocab.strings、doc.vocab.strings或者span.doc.vocab.string。

当某模块需要在某些标记上获得更快的处理速度时,可以使用C语言类型的64位哈希码代替字符串来实现。调用StringStore查找表将返回与该哈希码相关联的Python unicode字符串。

但是spaCy能做的可不仅仅只有这些,它还允许我们访问文档和词汇表完全填充的C语言类型结构,我们可以在Cython循环中使用这些结构,而不必去构建自己的结构。

spaCy拓展:

https://spacy.io/api/cython?source=post_page---------------------------

建立一个脚本用于创建一个包含有 10 份文档的列表,每份文档都大概含有 17 万个单词,采用 spaCy 进行分析。当然我们也可以对 17 万份文档(每份文档包含 10 个单词)进行分析,但是这样做会导致创建的过程非常慢,所以我们还是选择了 10 份文档。

我们想要在这个数据集上展开某些自然语言处理任务。例如,我们可以统计数据集中单词「run」作为名词出现的次数(例如,被 spaCy 标记为「NN」词性标签)。

采用Python循环来实现上述分析过程非常简单和直观:

importurllib.request

importspacy

withurllib.request.urlopen('https://raw.githubusercontent.com/pytorch/examples/master/word_language_model/data/wikitext-2/valid.txt') asresponse:

text = response.read

nlp = spacy.load('en')

doc_list = list(nlp(text[:800000].decode('utf8')) fori inrange(10))

这段代码至少需要运行 1.4 秒才能获得答案。如果我们的数据集中包含有数以百万计的文档,为了获得答案,我们也许需要花费超过一天的时间。

我们也许能够采用多线程来实现加速,但是在Python中这种做法并不是那么明智,因为你还需要处理全局解释器锁(GIL)。在Cython中可以无视GIL的存在而尽情使用线程加速。但不能再使用Python中的字典和列表,因为Python中的变量都自动带了锁(GIL)。还好Cython已经封装了C++标准库中的容器:deque,list,map,pair,queue,set,stack,vector。完全可以替代Python的dict, list, set等。

我们使用Cython就可以解决这个,但不能再使用Python中的字典和列表,因为Python中的变量都自动带了锁(GIL)。还好Cython已经封装了C++标准库中的容器:deque,list,map,pair,queue,set,stack,vector。完全可以替代Python的dict, list, set等。

另外请注意,Cython也可以使用多线程!Cython在后台可以直接调用OpenMP。

https://cython.readthedocs.io/en/latest/src/userguide/parallelism.html?source=post_page---------------------------

现在让我们尝试使用spaCy和Cython来加速 Python 代码。

首先需要考虑好数据结构,我们需要一个C类型的数组来存储数据,需要指针来指向每个文档的 TokenC 数组。我们还需要将测试字符(「run」和「NN」)转成 64 位哈希码。

当所有需要处理的数据都变成了C类型对象,我们就可以以纯C语言的速度对数据集进行迭代。

以下是被转换成Cython和spaCy的实现:

%%cython -+

importnumpy

fromcymem.cymem cimport Pool

fromspacy.tokens.doc cimport Doc

fromspacy.typedefs cimport hash_t

fromspacy.structs cimport TokenC

cdef struct DocElement:

TokenC* c

int length

cdef int fast_loop(DocElement* docs, int n_docs, hash_t word, hash_t tag):

cdef int n_out = 0

fordoc indocs[:n_docs]:

forc indoc.c[:doc.length]:

ifc.lex.lower == word andc.tag == tag:

n_out += 1

returnn_out

defmain_nlp_fast(doc_list):

cdef int i, n_out, n_docs = len(doc_list)

cdef Pool mem = Pool

cdef DocElement* docs = mem.alloc(n_docs, sizeof(DocElement))

cdef Doc doc

fori, doc inenumerate(doc_list):

docs[i].c = doc.c

docs[i].length = (doc).length

word_hash = doc.vocab.strings.add('run')

tag_hash = doc.vocab.strings.add('NN')

n_out = fast_loop(docs, n_docs, word_hash, tag_hash)

在Jupyter notebook上,这段Cython代码运行了大概20毫秒,比之前的纯Python循环快了大概80倍。

使用Jupyter notebook单元编写模块的速度很可观,它可以与其它 Python 模块和函数自然地连接:在 20 毫秒内扫描大约 170 万个单词,这意味着我们每秒能够处理高达 8 千万个单词。

如果你已经了解C语言,Cython还允许访问C代码,而Cython的创建者还没有为这些代码添加现成的声明。例如,使用以下代码,可以为C函数生成Python包装器并将其添加到模块dict中。

%%cython

cdef extern from"math.h":

cpdef double sin(double x)

Cython注意的坑

1、.pyx中用CDEF定义的东西,除类以外对的.py都是不可见的。

2、.c中是不能操作C类型的,如果想在.py中操作C类型就要在.pyx中从python对象转成C类型或者用含有set / get方法的C类型包裹类。

3、虽然Cython能对Python的str和C的“char *”之间进行自动类型转换,但是对于“char a [n]”这种固定长度的字符串是无法自动转换的。需要使用Cython的libc.string .strcpy进行显式拷贝。

4、回调函数需要用函数包裹,再通过C的“void *”强制转换后才能传入C函数。

Cython相关资料(下载)

0、其他:

https://cython.org/?source=post_page---------------------------

1、官方文档:

2、参考书籍(文末下载):

书籍下载

在后台输入(严格大小写)

责任编辑:

cython python3_30倍!使用Cython加速Python代码相关推荐

  1. 用GPU加速python代码的运算速度

    用GPU加速python代码的运算速度 1. Numba是什么? Numba是一个库,可以在运行时将Python代码编译为本地机器指令,而不会强制大幅度的改变普通的Python代码(稍后再做说明).翻 ...

  2. 用Cython加速Python代码,快到起飞!

    点击⬆️"小詹学Python",选择"星标"公众号 重磅干货,第一时间送达 编译 | sunlei 发布 | ATYUN订阅号 本文介绍 如果您曾经用Pytho ...

  3. python打包无法识别numba_用 Numba 加速 Python 代码

    原文出自微信公众号:Python那些事 一.介绍 pip install numba Numba 是 python 的即时(Just-in-time)编译器,即当你调用 python 函数时,你的全部 ...

  4. 用 Numba 加速 Python 代码

    1.介绍 Numba 是 python 的即时(Just-in-time)编译器,即当您调用 python 函数时,您的全部或部分代码就会被转换为"即时"执行的机器码,它将以您的本 ...

  5. numba加速python代码

    前言 说道现在最流行的语言,就不得不提python.可是python虽然容易上手,但速度却有点感人.如何用简单的方法让python加速到近乎可以媲美C的速度呢?今天来就来谈谈numba这个宝贝.对你没 ...

  6. Python性能优化指南--让你的Python代码快x3倍的秘诀

    Python性能优化指南 Python最为人诟病的就是其执行速度.如何让Python程序跑得更快一直是Python核心团队和社区努力的方向.作为Python开发者,我们同样可以采用某些原则和技巧,写出 ...

  7. python代码转成php代码的工具 或者go转成php的代码,想把odoo改成成php swoole当成web服务+go的架构

    目前市场上有一些可以将Python代码转换为PHP代码的工具,例如: Transcrypt:Transcrypt是一个将Python代码转换为JavaScript和PHP的工具.它可以将Python代 ...

  8. python代码封装加密_静态编译python源代码,使用Nuitka加密你的py源码

    Nuitka,100%兼容标准python2/python3,静态编译你的python程序 1. 概述 Nuitka(nuitka.net)可以将python代码转换为C++,然后编译为可执行文件,其 ...

  9. Numba加速Python教程

    Numba加速Python代码教程 Numba介绍 Numba可运行环境 Numba安装 Numba教程 Numba简单示例 Numba装饰器 Numba理解 什么是nopython模式? 如何衡量N ...

最新文章

  1. 数制系统之间的转换总结(各进制的转换)
  2. 用windows自带压缩备份mysql_Windows下配合计划任务的Mysql定时压缩备份
  3. 支持向量机(理论+opencv实现)
  4. boost::hana::not_用法的测试程序
  5. 【jenkins】jenkins按分支build和email
  6. [你必须知道的.NET]第三十一回,深入.NET 4.0之,从“新”展望
  7. mysql为什么不驼峰_为什么不用驼峰命名创建表名和字段?
  8. python创意小作品-python turtle库的几个小demo
  9. 智慧城市无人机倾斜摄影三维建模取得的成果展示
  10. 利用tushare数据计算期货主力合约的活跃度
  11. 【汇正财经】两市缩量下跌
  12. 计算机硬件开关打开无线网络适配器,ibm笔记本电脑无线硬件开启步骤
  13. SpringMVC注解@valid与@validata,@null,@notblank,@NotEmpty
  14. 【重识云原生】第六章容器基础6.4.7.1节——K8S Job组件
  15. 1、登录——邮件发送激活链接
  16. cpj-swagger分别整合struts2、spring mvc、servlet
  17. 大数据应用及未来展望
  18. mysql分布式主键_技术分享 | 优化 InnoDB 的主键
  19. 一文详解SLAM的主要任务和开源框架
  20. IAR Embedded Workbench 破解方法+工具+授权文件

热门文章

  1. Eclipse开发Android程序如何在手机上运行
  2. java 代码重用_Java 代码重用:功能与上下文重用
  3. golang mysql 插入_Mysql学习(一)添加一个新的用户并用golang操作Mysql
  4. viewsource和viewparsed_Network Panel说明
  5. 【机器学习】逻辑斯蒂回归概率计算和手动计算对比
  6. c语言递归汉诺塔次数,汉诺塔问题(C语言经典递归问题(一))
  7. curl查看swift状态命令_HTTP 请求与响应包括哪些,如何用Chrome查看 HTTP 请求与响应内容和curl 命令的使用...
  8. Linux:守护进程解析、如何实现守护进程
  9. django html显示xml,如何将HTML与Django集成?
  10. 新增成功到编制为空bug_36 个JS 面试题为你助力,让面试更有力(面试必读)