cython python3_30倍!使用Cython加速Python代码

原标题: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、参考书籍(文末下载):

书籍下载

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

责任编辑:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467893.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git安装及密钥的生成并上传本地文件到GitHub上

之前用的GitHub,不太熟练,一直在上传的过程中遇到了一些问题,看了网上诸多教程,总觉得很乱,特参考一些资料,总结了一篇完整的操作步骤,从下载安装到上传文件,亲测有效1.下载Git软件&…

100条常用写作谚语(1)(2)(3)(4)

文章目录勤奋 意志与成功学习方法与态度健康与心态品行与操守勤奋 意志与成功 where there is a will,there is a way有志者事竟成 No pains,no gains没有付出没有收获 Constant dropping wears away a stone水滴石穿,绳锯木断 Care and dil…

声明为数组定义为指针,声明为指针定义为数组

导语在这里我们做种强调的是在两个文件中,定义为数组声明为指针和定义为指声明为数组的这辆中情况。那么我们就需要两个源文件test.c和main.c。定义为数组,声明为指针test.cchar arr[] "abcdef";main.c#define _CRT_SECURE_NO_WARNINGS 1 #in…

100条常用写作谚语(5)(6)(7)(8)

文章目录金钱与财富珍惜时光择友与友谊常理与法则金钱与财富 Gold will not buy anything黄金不能买一切 The chief aim of man is not to get money 人的主要目的不是赚钱 The money the miser hoards will do him not good 守财奴积财,对自己毫无好处 What is we…

python中内置的集成开发工具_python应用(3):启用集成开发工具pycharm

之前写了个python程序给自己用,写代码时用的是macvim(vim的一种),macvim是个编辑工具,由于我已经设置过对python等各种语言的支持特性,所以什么缩进、对齐、高亮之类的表现都有,写起代码来非常舒服。可是,不…

python selenium环境配置Firefox和Chrome

1、下载Selenium库,可以使用pip install selenium https://pypi.python.org/pypi/selenium/ 2、下载驱动 Chrome: https://sites.google.com/a/chromium.org/chromedriver/downloads Firefox: https://github.com/mozilla/geckodriver/releases 3、配置环境变量 需要…

BUG_ON()、panic()、dump_stack()几种内核调试手段

Linux内核有一些方法可以用来方便标记bug,提供断言并输出信息。最常用的两个是BUG()和BUG_ON()。当被调用的时候,它们会引发oops,导致栈的回溯和错误信息的打印。这些声明会导致 oops跟硬件的体系结构是相关的。大部分体系结构把BUG()和BUG_O…

wordvba编程代码大全_这几本基础编程书籍一定要看

程序员书库(ID:OpenSourceTop) 编译书单来自:https://simpleprogrammer.com/best-programming-books-2019/关于程序员类的技术书籍有很多,但是往往没有时间阅读,下面的这些书籍,由John Sonmez精选,可以帮助…

经典DP

1.背包问题 (1)01背包 从n个重量和价值分别为wi,vi的物品,从中选出不超过W的物品,每种物品仅有一件,求所有方案中V的最大值。 最朴素最简单也最费时的方法:O(2^n) int rec(int i,int j)//从第i个开始挑选总…

C语言、嵌入式重点知识:回调函数

前言 上文分享了一个专用的双链表的基本操作示例:双链表的操作示例(附代码)这里提到了一个关键词:专用。与专用对应的词是通用。我们从字面上可以很容易理解这两个词,专用就是针对特定情况的,特点就是很有局…

python神经网络预测结果每次不一样_神经网络预测

神经网络预测时间:2019-12-09 12:34:00 作者:路由君 来源:路由器之家路由器之家网今天精心准备的是《神经网络预测》,下面是详解!bp神经网络预测是不是数据越多,预测能力就越好?不仅是神经网络,…

js数组的拷贝赋值复制二三事总结

今天在看React-native性能优化的时候,看到如何避免shouldComponentUpdate的异常数据时,脑内一阵风暴,从而牵连出一连串的问题,于是有了这一篇关于js数组的复制(深浅拷贝)与赋值等为何能产生异常数据的文章。…

今天我勇敢的点就一个gpio口

现在已经三月份了,时间过得超快,早上起来打开电脑,有点不习惯,微信群唧唧歪歪的那些股神今天不知为什么安静了。我喜欢看大家热闹的样子,更喜欢热闹的时候给我们发几个红包。我记得2015年,股市非常好&#…

Linux 通用gpio口驱动,rockchip

dts文件 gpio_rs485: gpio_rs485 {status = "okay";compatible = "gpio,px30-gpio";cname = "rs485";en-gpio = <&gpio3 12 GPIO_ACTIVE_HIGH>;}

android自定义控件

---恢复内容开始--- 1.新建一个类&#xff0c;继承View父类。重写一个或多个构造器后&#xff0c;在图编辑器里就有该控件可以拖动添加了。 2.若想使用Draw来绘制自己的控件。可以在View&#xff08;&#xff09;方法中使用Draw&#xff08;&#xff09;来绘制。 3.还可以定义控…

python语言中strike_Python学习笔记

嵌套函数作用域def make_adder(augend):def add(addend):return augend addendreturn add内部的函数可以访问外部函数scope内的变量&#xff0c;但是不能够重新对其赋值。如果重新赋值那么会在内部函数的scope内创建一个同名的本地变量(Python不允许对non-local变量赋值)。yie…

数据结构(4)

文章目录栈与队列栈队列![在这里插入图片描述](https://img-blog.csdnimg.cn/20200301182116946.png?x-oss-processimage/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzY2Mzc4MA,size_16,color_FFFFFF,t_70)栈与队列 栈 ha…

写一个公用的gpio口驱动

因为项目需要控制的GPIO口比较多&#xff0c;如果每个GPIO口都写一个驱动就显得比骄麻烦&#xff0c;所以就写了一个通用的GPIO口驱动。只要dts里面配置好设备GPIO相关信息就可以自动加载了。可以很充分的体现多个设备一个驱动的优良性。dts文件gpio_rs485: gpio_rs485 {status…

数据结构(5)

文章目录各种算法选择排序插入排序希尔排序***快速排序***归并排序二分查找各种算法 def bubble_sort(alist):"""冒泡排序"""n len(alist)for j in range(n-1):count 0for i in range(0, n-1-j):# 班长从头走到尾if alist[i] > alist[i1]:a…

大神们都应该去哪里工作?

接上一篇文章。文中提到我认识了一个做嵌入式的大神&#xff0c;技术真的非常厉害。我们在调试过程中&#xff0c;遇到问题&#xff0c;他总是能告诉我们排查的手段&#xff0c;而且针对一个问题&#xff0c;他能想到几种不同的解决方案。嵌入式驱动跟其他软件有点不一样&#…