大彩网-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代码

Cyt护肤品十大排行榜hon 言语是 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大彩网-30倍!运用Cython加快Python代码.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生成的大彩网-30倍!运用Cython加快Python代码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等。

别的请留意,C大彩网-30倍!运用Cython加快Python代码ython也可以运用多线程!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大彩网-30倍!运用Cython加快Python代码.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大彩网-30倍!运用Cython加快Python代码 indocs[:n_docs]:

forc indoc.c[:doc.length]:

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

n_out += 1

returnn_out

defmain_nlp_fast(doc_li大彩网-30倍!运用Cython加快Python代码st):

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

cdef Pool mem = Pool

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

cdef Doc doc

fori, doc inenumerate(doc_list):

docs[i].c = doc.c

docs[i].length = (<Doc>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材料