Python是一种高效的动态编程语言,广泛应用于科学、工程和数据分析应用领域。使python如此受欢迎的因素有很多,包括其干净的、表达性的语法和标准的数据结构,综合的“内置电池”标准库,优秀的文档,库和工具的广泛生态系统,专业支持的可用性,以及大而开放的社区。不过,也许最重要的原因是,像Python这样的动态类型化的解释语言能够提高生产率。Python足够敏捷与灵活,使它成为快速原型开发的一种伟大语言,同时也是构建完整系统的语言。
但是Python的最大优点也可能是它最大的缺点:它的灵活性和无类型的高级语法会导致数据和计算密集型程序的性能不佳。出于这个原因,关心效率的Python程序员经常在C中重写他们的最内层的循环,并从Python调用编译的C函数。有许多项目旨在简化这种优化,例如Cython,但这往往需要学习一种新的语法。理想情况下,Python程序员希望在不使用另一种编程语言的情况下使其现有的Python代码更快,当然,许多人也希望使用加速器来获得更高的性能。
Numba:高性能计算的高生产率
在这篇文章中,笔者将向你介绍一个来自Anaconda的Python编译器Numba,它可以在CUDA-capableGPU或多核cpu上编译Python代码。Python通常不是一种编译语言,你可能想知道为什么要使用Python编译器。答案当然是:运行本地编译的代码要比运行动态的、解译的代码快很多倍。Numba允许你为Python函数指定类型签名,从而在运行时启用编译(这就是“Just-in-Time”,即时,也可以说JIT编译)。Numba动态编译代码的能力意味着你不会因此而抛弃Python的灵活性。这是向提供高生产率编程和高性能计算的完美结合迈出的一大步。
使用Numba可以编写标准的Python函数,并在CUDA-capableGPU上运行它们。Numba是为面向数组的计算任务而设计的,很像大家常用的NumPy库。在面向数组的计算任务中,数据并行性对于像GPU这样的加速器是很自然的。Numba了解NumPy数组类型,并使用它们生成高效的编译代码,用于在GPU或多核CPU上执行。所需的编程工作可以很简单,就像添加一个函数修饰器来指示Numba为GPU编译一样。例如,在下面的代码中,
vectorizedecorator会生成一个编译的、矢量化的标量函数在运行时添加的版本,这样它就可以用于在GPU上并行处理数据数组。importnumpyasnp
fromnumbaimportvectorize
vectorize([float32(float32,float32)],target=cuda)defAdd(a,b):
returna+b
#Initializearrays
N=
A=np.ones(N,dtype=np.float32)
B=np.ones(A.shape,dtype=A.dtype)
C=np.empty_like(A,dtype=A.dtype)
#AddarraysonGPU
C=Add(A,B)
要在CPU上编译和运行相同的函数,我们只需将目标更改为“CPU”,它将在编译水平上带来性能,在CPU上向量化C代码。这种灵活性可以帮助你生成更可重用的代码,并允许你在没有GPU的机器上开发。
关于Python的GPU-Accelerated库
CUDA并行计算平台的优势之一是其可用的GPU加速库的阔度。Numba团队的另一个项目叫做pyculib,它提供了一个Python接口,用于CUDAcuBLAS(denselinearalgebra,稠密线性代数),cuFFT(FastFourierTransform,快速傅里叶变换),和cuRAND(randomnumbergeneration,随机数生成)库。许多应用程序都能够通过使用这些库获得显著的加速效果,而不需要编写任何特定于GPU的代码。例如,下面的代码使用“XORWOW”伪随机数生成器在GPU上生成万个均匀分布的随机数。
frompyculibimportrandascurand
prng=curand.PRNG(rndtype=curand.PRNG.XORWOW)
rand=np.empty()
prng.uniform(rand)
printrand[:10]
CUDAPython的高并行性
Anaconda(原名ContinuumAnalytics)认识到,在某些计算上实现大的速度需要一个更具表现力的编程接口,它比库和自动循环矢量化更详细地控制并行性。因此,Numba有另一组重要的特性,构成了其非正式名称“CUDAPython”。Numba公开了CUDA编程模型,正如CUDAC/C++,但是使用纯python语法,这样程序员就可以创建自定义、调优的并行内核,而不会放弃python带来的便捷和优势。Numba的CUDAJIT(通过decorator或函数调用可用)在运行时编译CUDAPython函数,专门针对你所使用的类型,它的CUDAPythonAPI提供了对数据传输和CUDA流的显式控制,以及其他特性。
下面的代码示例演示了一个简单的Mandelbrot设置内核。请注意,mandel_kernel函数使用Numba提供的cuda.threadIdx,cuda.blockIdx,cuda.blockDim和cuda.gridDim架构来计算当前线程的全局X和Y像素索引。与其他CUDA语言一样,我们通过插入在括号内一个“执行配置”(CUDA-speak用于线程数和线程块启动内核),在函数名和参数列表之间中:mandel_kernel[griddim,blockdim](-2.0,1.0,-1.0,1.0,d_image,20)。你还可以看到使用to_host和to_deviceAPI函数来从GPU中复制数据。
Mandelbrot的例子将在Github上持续更新。
cuda.jit(device=True)defmandel(x,y,max_iters):
"""
Giventherealandimaginarypartsofa