Python-摸索测试工具-把握代码口头效率-揭秘-代码速度 (python学校)
当咱们写完一个脚本或一个函数, 首先能保障失掉正确结果,其次尽或许的快 (只管会说Py这玩意咋整都慢,但有的名目就是得要基于Py开发)
本期将总结几种失掉程序运转期间的方法,极大的协助对比不同算法/写法效率
经常使用系统命令
每个操作系统都有自己的方法来算程序运转的期间,比如在PowerShell中,可以用Measure-Command来看一个/target=_blankclass=infotextkey>Python文件的运转期间
Measure-Command{pythontutorial.py}
在Ubuntu中 ,经常使用命令
timepythontutorial.py
假设咱们除了看整个Python脚本的运转期间外还想看看 部分 运转期间咋整
经常使用IPython的MagicCommand
假设你经常使用过如
JupyterNotebook
等工具会知道,他们用到了一个叫做IPython的交互式Python环境
在IPython中,有一个特意繁难的命令叫做
关于某行代码的测量可以经常使用:
关于某一个代码单元格的测量,可以经常使用
经常使用timeit
假设不用IPython咋整,没相关,曾经很凶猛了,Python有一个内置的模块,可以协助检测小段代码运转期间
可以在命令行界面运转如下命令
python-mtimeit'[iforiinrange(100)]'
经常使用timeit测量口头此列表推导式所需的期间,失掉输入
200000loops,bestof5:1.4usecperloop
此输入标明每次计时将口头200000次列表推导,合计时测试了5次,最好的结果是1.4毫秒
或许间接在Python中调用
importtimeitprint(timeit.timeit('[iforiinrange(100)]',number=1))
关于更复杂的状况,有三个参数须要思考:
比如一个更复杂的例子
importtimeit#prerequisitesbeforerunningthestmtmy_setup="frommathimportsqrt"#codesnippetwewouldliketomeasuremy_code='''defmy_function():forxinrange(10000000):sqrt(x)'''print(timeit.timeit(setup=my_setup,stmt=my_code,number=1000))#6.260000000000293e-05
经常使用time模块
Python中内置的模块置信都不生疏,基本的用法是在待测代码段的起始与末尾区分打上期间戳,而后取得期间差
importtimedefmy_function():foriinrange(10000000):passstart=time.perf_counter()my_function()print(time.perf_counter()-start)#0.1179838
我经常经常使用
time.perf_counter()
来失掉期间,更准确,在之前的教程中有提过
模块中还有一些其余计时选用
假设咱们须要在多个代码段测试运转期间,每个首尾都打上期间戳再计算期间差就有点繁琐了,咋整,上装璜器
importtimedeflog_execution_time(func):defwrer(*args,**kwargs):start=time.perf_counter()res=func(*args,**kwargs)end=time.perf_counter()print(f'Theexecutionof{func.__name__}used{end-start}seconds.')returnresreturnwrapper@log_execution_timedefmy_function():foriinrange(10000000):passmy_function()#Theexecutionofmy_functionused0.1156899seconds.
如上例所示,这样就使得代码肥肠洁净与整洁
使用Cython让python代码的速度提高30倍以上
毫无疑问,Python是社区最喜爱的编程语言!到目前为止,它是最容易使用的语言之一,因为python代码是用一种直观的、人类可读的方式编写的。
然而,你经常会反复听到一些对Python的抱怨,尤其是来自C语言爱好者的抱怨,这些抱怨无非就是Python很慢。
是的,他们并没有说错。
与许多其他编程语言相比,Python确实很慢。Benchmark game有一些比较不同编程语言在不同任务上的速度的可靠基准。
对于Python,我们有几种不同的方法可以加快速度:
如果你所做的实际上可以并行化,比如数据预处理或矩阵运算,这些都是很好的方法。
但是如果你的代码是纯Python的呢?如果你不得不使用一个很大的for循环,且不能将数据放入矩阵中,因为数据必须按顺序处理,那会怎样?有没有办法加快Python本身的速度呢?
答案是肯定的,这就是Cython来加速原生Python代码的地方。
什么是Cython?
Cython是Python和C/C++之间的一个中间步骤。它允许你编写纯Python代码,并且只需要做一些小修改,然后将其直接翻译成C代码。
使用Cython,我们将向该变量添加一个类型:
安装Cython只需要一行简单的pip命令:
Cython中的类型
使用Cython时,变量和函数分别有不同的类型。
对于变量我们有以下类型:
注意所有这些类型都来自C/C++ ! 而对于方法我们有以下类型:
了解了Cython类型之后,我们就可以直接实现加速了!
如何使用Cython加速你的代码
我们要做的第一件事是设置Python代码基准:用于计算数值阶乘的for循环。原生Python代码如下:
注意到该函数具有cpdef,以确保我们可以从Python中调用它。并且注意到到循环变量i是具有类型的。你需要为函数中的所有变量设置类型,以便C编译器知道使用哪种类型!
接下来,创建文件,该文件将Cython代码编译为C代码:
并执行编译:
Boom ! 可以看到我们的C代码已经编译好了,可以使用了!
你将看到,在Cython代码所在的文件夹中,你拥有运行C代码所需的所有文件,包括run_cython.c文件。如果你感兴趣,可以查看一下Cython生成的C代码!
现在我们准备测试我们新的并且超级快的C代码!查看下面的代码,它实现了一个速度测试,将原生Python代码与Cython代码进行比较。
代码非常直观,我们以与普通Python相同的方式导入文件,并以与普通Python相同的方式运行函数!
Cython几乎可以让你在所有原生Python代码上获得良好的加速,而不需要太多额外的工作。需要注意的关键是,循环次数越多,处理的数据越多,Cython可以提供的帮助就越多。
下表显示了Cython为不同的数值阶乘带来的加速性能。当数值为的时候,可以看到,我们的Cython加速超过了36倍。
几个提升Python运行效率的方法之间的对比
在我看来,python社区分为了三个流派,分别是python 2.x组织,3.x组织和PyPy组织。 这个分类基本上可以归根于类库的兼容性和速度。 这篇文章将聚焦于一些通用代码的优化技巧以及编译成C后性能的显著提升,当然我也会给出三大主要python流派运行时间。 我的目的不是为了证明一个比另一个强,只是为了让你知道如何在不同的环境下使用这些具体例子作比较。 使用生成器一个普遍被忽略的内存优化是生成器的使用。 生成器让我们创建一个函数一次只返回一条记录,而不是一次返回所有的记录,如果你正在使用python2.x,这就是你为啥使用xrange替代range或者使用ifilter替代filter的原因。 一个很好地例子就是创建一个很大的列表并将它们拼合在一起。 import timeitimport random def generate(num):while num:yield (10)num -= 1 def create_list(num):numbers = []while ((10))num -= 1return numbersprint((sum(generate(999)), setup=from __main__ import generate, number=1000))>>> 0. #Python 2.7>>> 1.2832 #Python 3.2print((sum(create_list(999)), setup=from __main__ import create_list, number=1000))>>> 0.4 #Python 2.7>>> 1. #Python 3.2这不仅是快了一点,也避免了你在内存中存储全部的列表!Ctypes的介绍对于关键性的性能代码python本身也提供给我们一个API来调用C方法,主要通过 ctypes来实现,你可以不写任何C代码来利用ctypes。 默认情况下python提供了预编译的标准c库,我们再回到生成器的例子,看看使用ctypes实现花费多少时间。 import timeitfrom ctypes import cdll def generate_c(num):#Load standard C librarylibc = (.6) #Linux#libc = #Windowswhile num:yield () % 10num -= 1 print((sum(generate_c(999)), setup=from __main__ import generate_c, number=1000))>>> 0.5 #Python 2.7>>> 0. #Python 3.2仅仅换成了c的随机函数,运行时间减了大半!现在如果我告诉你我们还能做得更好,你信吗?Cython的介绍Cython 是python的一个超集,允许我们调用C函数以及声明变量来提高性能。 尝试使用之前我们需要先安装Cython. sudo pip install cythonCython 本质上是另一个不再开发的类似类库Pyrex的分支,它将我们的类Python代码编译成C库,我们可以在一个python文件中调用。 对于你的python文件使用后缀替代后缀,让我们看一下使用Cython如何来运行我们的生成器代码。 #cython_ random def generate(num):while num:yield (10)num -= 1我们需要创建个以便我们能获取到Cython来编译我们的函数。 from import setupfrom import Extensionfrom import build_ext setup(cmdclass = {build_ext: build_ext},ext_modules = [Extension(generator, [cython_])])编译使用: python build_ext --inplace你应该可以看到两个文件cython_generator.c 文件 和 文件,我们使用下面方法测试我们的程序: import timeitprint((sum((999)), setup=import generator, number=1000))>>> 0.5还不赖,让我们看看是否还有可以改进的地方。 我们可以先声明“num”为整形,接着我们可以导入标准的C库来负责我们的随机函数。 #cython_ extern from stdlib.h:int c_libc_rand rand() def generate(int num):while num:yield c_libc_rand() % 10num -= 1如果我们再次编译运行我们会看到这一串惊人的数字。 >>> 0.8仅仅的几个改变带来了不赖的结果。 然而,有时这个改变很乏味,因此让我们来看看如何使用规则的python来实现吧。 PyPy的介绍PyPy 是一个Python2.7.3的即时编译器,通俗地说这意味着让你的代码运行的更快。 Quora在生产环境中使用了PyPy。 PyPy在它们的下载页面有一些安装说明,但是如果你使用的Ubuntu系统,你可以通过apt-get来安装。 它的运行方式是立即可用的,因此没有疯狂的bash或者运行脚本,只需下载然后运行即可。 让我们看看我们原始的生成器代码在PyPy下的性能如何。 import timeitimport random def generate(num):while num:yield (10)num -= 1 def create_list(num):numbers = []while ((10))num -= 1return numbersprint((sum(generate(999)), setup=from __main__ import generate, number=1000))>>> 0.3 #PyPy 1.9>>> 0.9 #PyPy 2.0b1print((sum(create_list(999)), setup=from __main__ import create_list, number=1000))>>> 0.1 #PyPy 1.9>>> 0.6 #PyPy 2.0b1哇!没有修改一行代码运行速度是纯python实现的8倍。 进一步测试为什么还要进一步研究?PyPy是冠军!并不全对。 虽然大多数程序可以运行在PyPy上,但是还是有一些库没有被完全支持。 而且,为你的项目写C的扩展相比换一个编译器更加容易。 让我们更加深入一些,看看ctypes如何让我们使用C来写库。 我们来测试一下归并排序和计算斐波那契数列的速度。 下面是我们要用到的C代码(functions.c): /* functions.c */#include #include #include /**/inline voidmerge (int *left, int l_len, int *right, int r_len, int *out){int i, j, k;for (i = j = k = 0; i < l_len && j < r_len;)out[k++] = left[i] < right[j] ? left[i++] : right[j++];while (i < l_len)out[k++] = left[i++];while (j < r_len)out[k++] = right[j++];} /* inner recursion of merge sort */voidrecur (int *buf, int *tmp, int len){int l = len / 2;if (len 在Linux平台,我们可以用下面的方法把它编译成一个共享库: gcc -Wall -fPIC -c -shared -o functions.o使用ctypes, 通过加载””这个共享库,就像我们前边对标准C库所作的那样,就可以使用这个库了。 这里我们将要比较Python实现和C实现。 现在我们开始计算斐波那契数列:# from ctypes import *import time libfunctions = (./) def fibRec(n):if n < 2:return nelse:return fibRec(n-1) + fibRec(n-2) start = ()fibRec(32)finish = ()print(Python: + str(finish - start)) # C Fibonaccistart = ()x = (32)finish = ()print(C: + str(finish - start))正如我们预料的那样,C比Python和PyPy更快。 我们也可以用同样的方式比较归并排序。 我们还没有深挖Cypes库,所以这些例子并没有反映python强大的一面,Cypes库只有少量的标准类型限制,比如int型,char数组,float型,字节(bytes)等等。 默认情况下,没有整形数组,然而通过与c_int相乘(ctype为int类型)我们可以间接获得这样的数组。 这也是代码第7行所要呈现的。 我们创建了一个c_int数组,有关我们数字的数组并分解打包到c_int数组中主要的是c语言不能这样做,而且你也不想。 我们用指针来修改函数体。 为了通过我们的c_numbers的数列,我们必须通过引用传递merge_sort功能。 运行merge_sort后,我们利用c_numbers数组进行排序,我已经把下面的代码加到我的文件中了。 #Python Merge Sortfrom random import shuffle, sample #Generate 9999 random numbers between 0 and numbers = sample(range(), 9999)shuffle(numbers)c_numbers = (c_int * len(numbers))(*numbers) from heapq import mergedef merge_sort(m):if len(m) 这儿通过表格和图标来比较不同的结果。 .
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。