罗兰在MATLAB的艺术

把想法变成MATLAB

请注意

罗兰在MATLAB的艺术已经存档,不会被更新。

曼德尔勃特集合的GPU

今天我欢迎回来客人博客本Tordoff谁最后博客如何雕刻一只恐龙。本作品在这些天并行计算工具箱™特别关注gpu。

内容

关于这个演示

Benoit Mandelbrot去年秋天去世,尽管发明这个词“分形”和分形几何的先驱之一,他的持久遗产对于大多数人来说无疑是分形的,是以他的名字命名的。可以有一些数学家和数学算法,尽可能多的青少年卧室墙壁装饰曼德布洛特的设置在90年代末80年代初的。

我也怀疑没有其他分形已经实现在许多不同的语言在不同的平台上。我的第一个版本是一个块状32 x24版本老龄化ZX频谱和七个辉煌的色彩。这些天我们有一个更多的图形能力,和更多的颜色。事实上,正如我们将要看到的,像了曼德尔勃特集合的算法是适合运行在GPU(图形处理器)大多都闲置在你的电脑。

作为一个起点,我将使用一个版本的曼德尔勃特集合松散取自克里夫硅藻土与MATLAB实验电子书。我们将看的三种不同的方式并行计算工具箱™提供了使用GPU加速这个:

  1. 使用现有的算法,但GPU数据作为输入
  2. 使用arrayfun独立对每个元素执行算法
  3. 使用MATLAB / CUDA接口运行一些现有的CUDA / c++代码

正如您将看到的,这三种方法是越来越难,但偿还巨大的努力增加速度。选择一个方法来权衡困难和速度是典型的并行问题。

设置

下面的值指定高度放大了曼德尔勃特集合的一部分之间的谷主心形和p / q灯泡了。

下面的代码形式的起点为每个网格计算通过创建一个1000 x1000真正的部分(X)和虚部之间(Y)这些限制。对于这个位置我碰巧知道500次迭代足以画一个漂亮的图片。

maxIterations = 500;gridSize = 1000;xlim = [-0.748766713922161, -0.748766707771757];ylim = [0.123640844894862, 0.123640851045266];

在MATLAB了曼德尔勃特集合

下面是一个使用标准的MATLAB实现了曼德尔勃特集合命令在CPU上运行。这个计算是矢量化,这样每一个位置更新。

%设置t =抽搐();xlim x = linspace (xlim (1), (2), gridSize);ylim y = linspace (ylim (1), (2), gridSize);[xGrid, yGrid] = meshgrid (x, y);z0 = xGrid + 1我* yGrid;数= 0(大小(z0));%计算z = z0;n = 0: maxIterations z z =。* z + z0;在= abs (z) < = 2;数=数+内部;结束数=日志(数+ 1);%显示cpuTime = toc (t);集(gcf,“位置”,(200 200 600 600));显示亮度图像(x, y,数);轴图像colormap([飞机();flipud(飞机());0 0 0));标题(sprintf (' % 1.2 fsecs (GPU) 'cpuTime));

使用parallel.gpu。GPUArray- 16 Times Faster

parallel.gpu.GPUArray提供GPU版本的许多功能,可以用于创建数据数组,包括linspace,logspace,meshgrid功能需要。类似地,数组初始化直接在GPU上使用函数parallel.gpu.GPUArray.zeros

除了这些简单的数据初始化的变化,该算法不变(和> 16倍):

%设置t =抽搐();x = parallel.gpu.GPUArray。linspace(xlim(1), xlim(2), gridSize ); y = parallel.gpu.GPUArray.linspace( ylim(1), ylim(2), gridSize ); [xGrid,yGrid] = meshgrid( x, y ); z0 = complex( xGrid, yGrid ); count = parallel.gpu.GPUArray.zeros( size(z0) );%计算z = z0;n = 0: maxIterations z z =。* z + z0;在= abs (z) < = 2;数=数+内部;结束数=日志(数+ 1);%显示naiveGPUTime = toc (t);显示亮度图像轴(x, y,计数)图像标题(sprintf (' % 1.2 fsecs(天真的GPU) = % 1.1外汇更快,naiveGPUTime cpuTime / naiveGPUTime))

使用Element-wise操作- 164倍

注意到该算法操作同样在输入的每一个元素,我们可以把一个helper函数中的代码和调用使用arrayfun。GPU数组输入的函数使用arrayfun被编译成原生GPU的代码。在这种情况下,我们把循环processMandelbrotElement.m看起来如下:

函数计算= processMandelbrotElement (x0, y0, maxIterations) z0 =复杂(x0, y0);z = z0;数= 1;而(数< = maxIterations)…& &((真正的(z) * (z) +图像放大(z) *图像放大(z)) < = 4)数=计数+ 1;z = * z + z0;结束数=日志(数);

注意,提前中止引入了因为这个函数只处理单个元素。对于大多数的看法了曼德尔勃特集合大量的元素很早就停止,这可以节省许多处理。的循环也被取代了循环,因为他们通常更有效率。这个函数没有提及的GPU和不使用任何GPU-specific特性——它是标准的MATLAB代码。

使用arrayfun意味着,而不是成千上万的调用单独GPU-optimized操作(至少6每个迭代),我们打一个电话到GPU并行操作,执行整个计算。这会显著减少开销——快164倍。

%设置t =抽搐();x = parallel.gpu.GPUArray。linspace(xlim(1), xlim(2), gridSize ); y = parallel.gpu.GPUArray.linspace( ylim(1), ylim(2), gridSize ); [xGrid,yGrid] = meshgrid( x, y );%计算数= arrayfun (@processMandelbrotElement xGrid、yGrid maxIterations);%显示gpuArrayfunTime = toc (t);显示亮度图像轴(x, y,计数)图像标题(sprintf (' % 1.2 fsecs (GPU arrayfun) = % 1.1外汇更快的,gpuArrayfunTime cpuTime / gpuArrayfunTime));

在CUDA - 340倍

MATLAB实验改进的性能是通过转换C-Mex函数的基本算法。如果你愿意做一些工作在C / c++中,然后您可以使用并行计算工具箱使用MATLAB调用预先写好的CUDA内核数据。这是使用工具箱来完成的parallel.gpu.CUDAKernel特性。

CUDA / c++实现手写在元素的处理算法processMandelbrotElement.cu。这必须是手工编制学校网站使用nVidia的编译器来生成程序processMandelbrotElement.ptx(.ptx代表“并行线程执行语言”)。

CUDA / c++代码稍微比我们见过的MATLAB版本涉及到目前为止由于缺乏c++的复数。然而,该算法的本质是不变的:

__device__ unsigned int doIterations(双const realPart0,双const imagPart0, unsigned int const maxIters){/ /初始化:z = z0双realPart = realPart0;双imagPart = imagPart0;无符号整型数= 0;/ /循环直到逃脱而((数< = maxIters) & & ((realPart * realPart + imagPart * imagPart) < = 4.0)){+ +计数;/ /更新:z = * z + z0;双const oldRealPart = realPart;realPart = realPart * realPart - imagPart * imagPart + realPart0;imagPart = 2.0 * oldRealPart * imagPart + imagPart0;}返回计数;}

(完整的源代码可以在文件交换提交链接。)

一个GPU线程需要数组中的每个元素,分成块。内核表明一个线程阻塞有多大,这是用来计算块的数量。

%设置t =抽搐();x = parallel.gpu.GPUArray。linspace(xlim(1), xlim(2), gridSize ); y = parallel.gpu.GPUArray.linspace( ylim(1), ylim(2), gridSize ); [xGrid,yGrid] = meshgrid( x, y );%加载内核内核= parallel.gpu.CUDAKernel (“processMandelbrotElement.ptx”,“processMandelbrotElement.cu”);%确保我们有足够的块覆盖整个数组numElements =元素个数(xGrid);内核。ThreadBlockSize = (kernel.MaxThreadsPerBlock 1 1);内核。GridSize = [(numElements / kernel.MaxThreadsPerBlock),即:1);%调用内核数= parallel.gpu.GPUArray。0(大小(xGrid));数=函数宏指令(内核、计数xGrid、yGrid maxIterations, numElements);%显示gpuCUDAKernelTime = toc (t);显示亮度图像轴(x, y,计数)图像标题(sprintf (' % 1.2 fsecs (GPU CUDAKernel) = % 1.1外汇更快的,gpuCUDAKernelTime cpuTime / gpuCUDAKernelTime));

总结

当我的兄弟和我坐下来,编码我们首先曼德尔勃特集合它接管了一分钟来呈现一个32 x24形象。我们已经看到一些简单的步骤和一些不错的硬件可以呈现1000 x1000图像几帧每秒。时代变了!

我们看了加快基本的MATLAB实现的三种方式:

  1. 将输入数据转换成在GPU上使用gpuArray,使得该算法不变
  2. 使用arrayfun在一个GPUArray输入的每个元素上执行该算法独立输入
  3. 使用parallel.gpu.CUDAKernel一些现有的CUDA / c++代码运行使用MATLAB数据

我还创建了一个图形化应用程序,允许您使用这些方法探索了曼德尔勃特集合。你可以下载这个文件交换:

如果你有任何的想法创建Mandelbrot集以更大的速度或能想到的其他可能充分利用GPU的算法,请留下你的评论和问题下面

引用

阅读更多关于Benoit Mandelbrot生命和时间:

看到他最近在TED的讲话:

看一看克里夫硅藻土的章了曼德尔勃特集合:

版权2011年MathWorks公司。


使用MATLAB®7.12发表


评论

留下你的评论,请点击在这里MathWorks账户登录或创建一个新的。