这个例子演示了如何使用GPU的先进功能可以使用MEX文件进行访问。它建立在例如在GPU上的模板操作。前面的示例使用Conway的“生命游戏”,展示怎么操作模板可以使用MATLAB®代码在GPU上运行进行。本实例演示了如何进一步提高使用GPU两种先进的功能模板操作的性能:共享内存和纹理内存。您可以通过在MEX文件中写入自己的CUDA代码,并要求从MATLAB的MEX文件做到这一点。你可以找到介绍了使用GPU的MEX中的文件文档。
如先前实例中所定义,在“模版操作”时,输出数组的每个元素取决于输入阵列的小区域。实例包括有限差,卷积,中值滤波,和有限元方法。如果我们假设模板操作是我们的工作流程的一个关键部分,我们可以采取将其转换为手写CUDA内核从GPU获得最大利益的希望的时间。这个例子使用Conway的“生命游戏”为模板的操作,计算移动到一个MEX文件。
“生命游戏”遵循一些简单的规则:
单元被排列成一个二维网格
在每一个步骤,每一个细胞的命运是由八个最近的邻居的生命力确定
正好有三只活邻居的细胞来生活在接下来的步骤
正好用两个现场的邻居活细胞在下一步仍然活着
所有其他细胞(包括那些具有多于三个邻居)模具在下一步骤或保持空
因此,“模版”在这种情况下是每个元件周围的3×3区域。更多细节,查看paralleldemo_gpu_stencil代码。
这个例子是为了让我们使用子功能的函数:
功能paralleldemo_gpu_mexstencil()
单元格的初始群在二维网格活着的位置的大约25%创建。
gridSize = 500;numGenerations = 100;initialGrid =(RAND(gridSize,gridSize)> 0.75);保持离于imagesc(initialGrid);颜色表([1 1 1 0 0.5 0]);标题(“初始网格”);
为了获得性能基准,我们先从中所描述的初步实现在MATLAB实验。这个版本可以通过简单地使在GPU上运行确保初始种群是在使用了GPUgpuArray
。
功能X = updateGrid(X,N)P = [1 1:N-1];Q = [2:N N];%计算有多少的八个邻居是活的。邻居= X(:,P)+ X(:,Q)+ X(P,:) + X(Q,:) +...X(P,P)+ X(Q,Q)+ X(P,Q)+ X(Q,P);%有两个活的邻居活细胞,或与任何细胞%三个活邻居,在下一步活着。X =(X(邻居== 2))|(邻居== 3);结束currentGrid = gpuArray(initialGrid);%循环每一个生成更新所述网格并显示它对于代= 1:numGenerations currentGrid = updateGrid(currentGrid,gridSize);于imagesc(currentGrid);标题(num2str(代));的DrawNow;结束
现在重新运行游戏并测量需要多长时间为每一代。
%该功能定义外循环调用每一代,而不%做显示。功能格= callUpdateGrid(网格,gridSize,N)对于根= 1:N =格updateGrid(网格,gridSize);结束结束gpuInitialGrid = gpuArray(initialGrid);%保留这一结果验证下面每个版本的正确性。expectedResult = callUpdateGrid(gpuInitialGrid,gridSize,numGenerations);gpuBuiltinsTime = gputimeit(@()callUpdateGrid(gpuInitialGrid,...gridSize,numGenerations));fprintf中(“在GPU上的平均时间:每一代%2.3fms \ N”,...1000 * gpuBuiltinsTime / numGenerations);
在GPU上的平均时间:每一代1.528ms
当写模版操作的CUDA内核版本中,我们必须将输入数据分割成在其上每个线程块可以操作块。块中的每个线程将被读取,也需要通过在块的其他线程的数据。以最小化读取操作的数目的一种方法是处理之前所需的输入数据复制到共享存储器。此副本必须包括一些周边元素,让该块边缘的正确计算。对于生活,我们的模版只是元素的3×3平方的比赛,我们需要一个元素边界。例如,对于3×3使用块处理的9x9的网格,第五块将突出显示的区域,其中,所述黄色元素是“卤代”还必须阅读操作。
我们已说明了这一做法到文件的CUDA代码pctdemo_life_cuda_shmem.cu
。此文件中的CUDA设备功能操作如下:
所有线程拷贝输入电网到共享存储器,包括卤代的相关部分。
螺纹彼此同步以确保共享内存已经准备就绪。
适合输出格栅线条进行寿命计算的游戏。
在该文件中的主机代码调用一次为每个代CUDA设备功能,采用CUDA运行时API。它使用的输入和输出两个不同的写入缓冲区。在每次迭代中,MEX文件交换的输入和输出指针,以便没有复制是必需的。
为了调用从MATLAB的功能,我们需要从MATLAB解开输入数组一个MEX网关,建立在GPU工作区,并返回输出。该MEX网关功能可以在文件中找到pctdemo_life_mex_shmem.cpp
。
之前,我们可以调用MEX文件,我们必须使用编译mexcuda
,这就需要安装NVCC
编译器。您可以使用像这样的命令这两个文件编译成一个单一的MEX函数
mexcuda - 输出pctdemo_life_mex_shmem ... pctdemo_life_cuda_shmem.cu pctdemo_life_mex_shmem.cpp
这会产生一个名为MEX文件pctdemo_life_mex_shmem
。
%计算使用与共享存储器中的文件MEX的输出值。该%的初始输入值被复制到MEX文件内的GPU。格= pctdemo_life_mex_shmem(initialGrid,numGenerations);gpuMexTime = gputimeit(@()pctdemo_life_mex_shmem(initialGrid,...numGenerations));%打印出的平均计算时间和检查结果不变。fprintf中('每代%2.3fms的平均时间(%1.1fx更快)。\ N',...1000 * gpuMexTime / numGenerations,gpuBuiltinsTime / gpuMexTime);断言(ISEQUAL(网格,expectedResult));
每一代0.055ms的平均时间(27.7x更快)。
应对反复的读取操作的问题的第二种方法是使用GPU的纹理内存。纹理访问都在某种程度上缓存当几个线程访问重叠的2-d的数据的尝试,以提供良好的性能。这正是我们在模板操作的存取模式。
有迹象表明,可以用来读取纹理内存中的两个CUDA的API。我们选择使用CUDA纹理参考API,它支持所有的CUDA设备。万博1manbetx这可以是数组中的元素的最大数量绑定到纹理是,所以如果输入有多个元素,质感的做法是行不通的。
这说明了这种方法的CUDA代码是在pctdemo_life_cuda_texture.cu
。正如在以前的版本中,这个文件包含主机代码和设备代码。这个文件的三个特征使得在设备功能的使用纹理内存。
纹理变量在MEX文件的顶部声明。
CUDA设备功能从纹理参考输入端。
该MEX-文件结合纹理引用到输入缓冲器。
在这个文件中,在CUDA设备的功能比以前更简单。它只需要进行寿命计算的游戏。有没有必要复制到共享内存或同步线程。
正如在共享存储器版本,主机代码调用一次为每个代CUDA设备功能,采用CUDA运行时API。它再次使用的输入和输出两个可写缓冲区,并在每次迭代交换它们的指针。每次调用设备功能之前,它结合纹理参考到相应的缓冲区中。装置功能已被执行之后,它解除绑定纹理参考。
目前这一版本的MEX文件网关,pctdemo_life_mex_texture.cpp
这需要输入和输出阵列和工作区分配的护理。这些文件可以通过内置到使用如下命令一个MEX文件。
mexcuda - 输出pctdemo_life_mex_texture ... pctdemo_life_cuda_texture.cu pctdemo_life_mex_texture.cpp
%计算使用带纹理的MEX文件的输出值。格= pctdemo_life_mex_texture(initialGrid,numGenerations);gpuTexMexTime = gputimeit(@()pctdemo_life_mex_texture(initialGrid,...numGenerations));%打印出的平均计算时间和检查结果不变。fprintf中('每代%2.3fms的平均时间(%1.1fx更快)。\ N',...1000 * gpuTexMexTime / numGenerations,gpuBuiltinsTime / gpuTexMexTime);断言(ISEQUAL(网格,expectedResult));
每一代0.025ms的平均时间(61.5x更快)。
在这个例子中,我们已经说明了两种不同的方式来处理复制的模板操作输入:块明确地复制到共享内存中,或利用GPU的纹理缓存的优势。最好的方法将取决于模具的大小,重叠区域的大小,和硬件代,GPU的。最重要的一点是,你可以结合使用这些方法都与你的MATLAB代码来优化您的应用程序。
fprintf中(“使用gpuArray第一个版本:每一代%2.3fms \ n”,...1000 * gpuBuiltinsTime / numGenerations);fprintf中([“MEX具有共享存储器:每代%2.3fms”,...'(%1.1fx更快)。\ N'],1000 * gpuMexTime / numGenerations,...gpuBuiltinsTime / gpuMexTime);fprintf中([“MEX与纹理存储器:每代%2.3fms”...'(%1.1fx更快)。\ N']”,1000 * gpuTexMexTime / numGenerations,...gpuBuiltinsTime / gpuTexMexTime);
使用gpuArray第一个版本:每一代1.528ms。MEX具有共享存储器:每代0.055ms(27.7x更快)。MEX与纹理存储器:每代0.025ms(61.5x更快)。
结束