当前位置: 首页 > 后端技术 > Python

Python算法提速的四种方法(二)Numba

时间:2023-03-25 21:46:01 Python

CDA数据分析师出品相信大家在做一些算法的时候,经常会被庞大的计算量带来的巨大的计算量所需要的时间所折磨数据的。接下来我们重点介绍四种方法,帮助你加快Python的计算时间,减少算法的等待时间。今天就给大家介绍一下Numba的内容。1.简介那么什么是Numba?Numba是Python的即时编译器,这意味着当你调用Python函数时,你的全部或部分代码将被计时并转换为机器码执行,然后以你本机的速度运行机器代码。Numba由AnacondaCorporation赞助并得到许多组织的支持。使用Numba,您可以加速计算密集型的所有计算密集型Python函数(例如循环)。它还支持numpy库!所以你也可以在你的计算中使用numpy并加速整体计算,因为python中的循环非常慢。也可以使用python标准库中数学库的很多函数,比如sqrt等。2.为什么选择Numba?那么,为什么要选择Numba?特别是当存在许多其他编译器时,如cython或任何其他类似的编译器,或类似pypy的编译器。选择Numba的原因很简单,因为您不需要离开用Python编写代码的舒适区。是的,您没看错,您不需要更改代码来加速数据,这与使用typedef从类似的cython代码获得的加速相当。那不是更好吗?您只需要在函数周围添加一个熟悉的Python功能,称为装饰器(包装器)。类装饰器目前正在开发中。所以,你只需要添加一个装饰器。例如:fromnumbaimportjit@jitdeffunction(x):#Loopornumericallyintensivecalculationreturnx它仍然看起来像纯python代码,不是吗?3.Numba是如何工作的?Numb使用LLVM编译器基础架构从纯Python代码生成优化的机器代码。使用Numba的代码运行速度与C、C++或Fortran中的类似代码一样快。以下是代码的编译方式:首先,Python函数被采用、优化并转换为Numba的中间表示,然后类似于Numpy的类型推断进行类型化(因此pythonfloat为float64),然后转换为LLVM可解释代码。然后将该代码提供给LLVM的即时编译器以发出机器代码。您可以在运行时生成代码,或根据需要将其导入CPU(默认)或GPU。4.使用基本的Numba功能(只需要@jit!)是小菜一碟!为了获得最佳性能,numba建议在jit包装器中使用参数nopython=True,但它根本不会使用Python解释器。或者你也可以使用@njit。如果你的nopython=True包装器因错误而失败,你可以使用简单的@jit包装器编译部分代码,循环它,把它变成一个函数,编译成机器码,然后把剩下的交给python口译员。因此,你只需要执行以下操作:fromnumbaimportnjit,jit@njit#or@jit(nopython=True)deffunction(a,b):#loopornumericallyintensivecalculationreturnresult使用@jit时,确保您的代码包含Numba可以编译的内容,例如计算密集型循环,使用它支持的库(Numpy)和它支持的函数。否则,它不会编译任何东西。首先,numba还会在这些函数首次用作机器码后缓存这些函数。因此,在第一次使用之后它变得更快,因为您不必再??次编译该代码,因为您使用的参数类型与之前使用的相同。而且,如果你的代码可以并行运行,你也可以将parallel=True作为参数传递,但是必须和参数nopython=True一起使用。目前,它仅适用于CPU。你也可以指定你想要的函数签名,但是它不会编译你给他的任何其他类型的参数,例如:你也可以指定你希望函数具有的函数签名,但是对于提供的任何其他类型的参数给它参数,它不会编译。例如:fromnumbaimportjit,int32@jit(int32(int32,int32))deffunction(a,b):#循环或者数值密集型计算返回结果#或者你没有导入类型的名字#你可以使用它们作为字符串传递@jit('int32(int32,int32)')deffunction(a,b):#Loopornumericallyintensivecalculationreturnresult现在,你的函数将只接受两个int32并返回一个int32。这样,您可以更好地控制您的功能。您甚至可以根据需要传递任意数量的函数签名。您还可以使用numba提供的其他装饰器:1.@vectorize:允许标量参数用作numpyufuncs,1.@guvectorize:生成NumPy通用ufuncs1。@stencil:将函数声明为类模板操作的内核,1.@jitclass:用于启用jit的类,1.@cfunc:声明一个函数用作本机回调(从C/C++等调用),1.@overload:注册自己的函数实现,用于nopython模式,比如@overload(scipy.special.j0)。Numba还具有提前(AOT)编译功能,可生成不依赖于Numba的已编译扩展模块。但是:1.它只允许常规函数(没有ufuncs),1.你必须指定一个函数签名。您只能指定一个,因为许多指定不同的名称。它还为您的CPU体系结构系列生成通用代码。5.@vectorize包装器通过使用@vectorize包装器,您可以将对标量进行操作的函数转换为数组,例如,如果您使用的是仅对标量进行操作的python库数学,则对数组进行操作。这提供了类似于numpy数组操作(ufuncs)的速度。例如:@vectorizedeffunc(a,b):#对标量返回结果进行操作你也可以将target参数传给这个wrapper,其值对于并行代码可以等于parallel,cuda对于incuda/的值在GPU上运行代码。@vectorize(target="parallel")deffunc(a,b):#对标量进行操作返回结果假设你的代码足够计算密集或者数组足够大,使用numpy进行矢量化target="parallel"or"cuda"通常比numpy实现运行得更快。如果不这样做,则需要花费大量时间来创建线程以及为不同线程拆分元素,这可能会超过整个过程的实际计算时间。所以这项工作应该足够繁重以加快速度。6.在GPU上运行函数你也可以像包装器一样传递@jit来在cuda/GPU上运行函数。为此,您必须从numba库中导入cuda。但是在GPU上运行代码不会像以前那么容易了。为了在GPU上的数百甚至数千个线程上运行一个函数,它需要进行一些初始计算。您必须声明和管理网格、块和线程层次结构。但这并不难。要在GPU上执行一个函数,你必须定义一个内核函数(kernelfunction)或者一个设备函数(devicefunction)。首先,让我们看一下核函数(kernelfunction)。关于内核函数需要记住的几件事:a)内核在调用时显式声明其线程层次结构,即块数和每个块的线程数。您可以编译一次内核,然后使用不同的块和网格大小多次调用它。b)内核无法返回值。因此,您将不得不对原始数组进行更改,或者传递另一个数组来存储结果。对于计算标量,您必须传递一元数组。Defineakernelfunctionfromnumbaimportcuda@cuda.jitdeffunc(a,result):#然后是一些CUDA相关的计算#你的计算密集型代码#你的答案存储在'result'所以,要启动内核,你必须传递两件事:1.每个块的线程数,以及1.块数。例如:threadsperblock=32blockspergrid=(array.size+(threadsperblock-1))//threadsperblockfuncblockspergrid,threadsperblock每个线程中的内核函数必须知道自己在哪个线程中,负责数组的哪个元素。使用Numba,元素的这些位置可以通过一次调用轻松获得。@cuda.jitdeffunc(a,result):pos=cuda.grid(1)#对于一维数组#x,y=cuda.grid(2)#对于二维数组ifpos