文档

改善小矩阵问题的表现在GPU上使用PAGEFUN

这个例子说明如何使用pagefun以提高在3-d环境中应用了大量的独立旋转和平移的物体的性能。这是典型的范围内,其涉及对小数组大批量的计算问题的。

当在非常大的矩阵上执行计算时,gpu是最有效的。在MATLAB®中,这通常是通过对代码进行向量化来最大化每条指令所做的工作来实现的。当你有一个大的数据集,但你的计算被分割成许多小的矩阵操作,它可以挑战最大化性能同时运行在数百个GPU核心。

arrayfunbsxfun函数允许在GPU上并行执行标量操作。的pagefun功能增加了以类似的方式分批开展矩阵运算的能力。的pagefun函数可在并行计算工具箱™中与gpuArrays一起使用。

在本例中,一个机器人正在导航一个已知的地图,其中包含机器人可以使用传感器识别的大量特征。机器人通过测量这些物体的相对位置和方向,并将它们与地图位置进行比较,从而在地图中定位自己。假设机器人没有完全迷路,它可以利用两者之间的任何差异来校正它的位置,例如使用卡尔曼滤波器。我们将集中讨论算法的第一部分。

这个例子是一个函数,以使辅助功能可以被嵌套在它里面。

函数paralleldemo_gpu_pagefun

设置地图

让我们在一个大房间中创建一个具有随机位置和方向的对象地图。

numObjects = 1000;roomDimensions = [50 50 5];%长*宽*高(米)

我们用3×1向量表示位置和方向T和3×3旋转矩阵R。当我们有N个这样的东西转换我们把平移放到3×3×n的矩阵中,把旋转放到3×3×n的数组中。下面的函数初始化N个随机值转换,提供一个结构作为输出:

函数随机变换(N)。T = 0 (3,n);Tform。R= zeros(3, 3, N);I = 1:N Tform.T(:,1)=兰特(3,1)* roomDimensions';%为了得到一个随机的方向,我们可以提取一个标准正交一个随机的3×3矩阵的%基。Tform.R(:,:,1)=正高(兰特(3,3));结束结束

现在使用它来设置对象变换的映射,以及机器人的开始位置。

地图= randomTransforms (numObjects);机器人= randomTransforms (1);

定义方程

要正确识别地图功能的机器人需要改造的地图把它的传感器在原点。然后,它可以通过对比它认为有什么期望看到找到地图对象。

对于映射对象我们可以找到它相对于机器人的位置和方向通过转换其全球地图位置:

在哪里机器人的位置和方向,和表示地图数据。等价的MATLAB代码如下:

Rrel(:,:,1)=的Rbot '* RMAP(:,:,i)的TREL(:,1)=的Rbot' *(TMAP(:,i)的 -  TBOT)

在CPU使用执行许多矩阵变换循环

我们需要将每个地图对象转换为相对于机器人的位置。我们可以通过依次循环所有的转换来串行地完成此操作。注意for的“like”语法0这将允许我们在下一节中使用相同的代码在GPU上。

函数Rel = loopingTransform(Robot, Map) Rel. r = zeros(size(Map. r))'喜欢', Map.R);%初始化内存Rel.T = 0(大小(Map.T),'喜欢', Map.T);%初始化内存r (:,:,i) = Robot. r (:,:,i)R的* Map.R(:,:我);Rel.T(:,我)=机器人。R' * (Map.T(:,i) - Robot.T);结束结束

为计算计时,我们使用timeit函数,它会调用loopingTransform多次来得到一个平均时间。因为它需要一个不带参数的函数,所以我们使用@()语法来创建正确形式的匿名函数。

CPUTIME = timeit(@()loopingTransform(机器人,地图));流(“这需要%3.4f秒在CPU上执行%d变换。\ N”,cpuTime numObjects);
这需要在CPU上0.0104秒执行1000个变换。

尝试在GPU上相同的代码

要在GPU上运行这段代码,仅仅是将数据复制到gpuArray。当MATLAB遇到存储在GPU上的数据时,只要支持,它就会使用这些数据在GPU上运行任何代码。万博1manbetx

gMap。R= gpuArray(Map.R); gMap.T = gpuArray(Map.T); gRobot.R = gpuArray(Robot.R); gRobot.T = gpuArray(Robot.T);

现在我们所说的gputimeit,等于timeit代码,其包括GPU计算。它可以确保所有GPU操作记录时间之前完成。

流(“计算…\ n”);gpuTime = gputimeit(@)loopingTransform(gRobot, gMap))流('在GPU上执行%3.4f转换需要%3.4f秒。\n',gpuTime numObjects);流([“未向量化的GPU代码慢了%3.2f”,'而不是CPU版本。\n'), gpuTime / cpuTime);
计算……在GPU上执行1000次转换需要0.5588秒。未向量化的GPU代码比CPU版本慢53.90倍。

使用批处理过程pagefun

上面的GPU版本非常慢,因为尽管所有的计算都是独立的,但它们是串行运行的。使用pagefun我们可以并行运行的所有计算。我们还聘请bsxfun来计算转换,因为这些是基于元素的操作。

函数Rel = pagefunTransform(Robot, Map) Rel. r = pagefun(@mtimes, Robot)R’,Map.R);Rel.T =机器人。R' * bsxfun(@minus, Map.T, Robot.T);结束gpuPagefunTime = gputimeit(@()pagefunTransform(gRobot, gMap));流([“这使用pagefun需要在GPU%3.4f秒”,'执行%d转换。\n', gpuPagefunTime numObjects);流([向量化GPU代码快了%3.2f,'而不是CPU版本。\n'), cpuTime / gpuPagefunTime);流([向量化GPU代码快了%3.2f,'而不是未向量化的GPU版本。\n'), gpuTime / gpuPagefunTime);
使用pagefun在GPU上执行1000次转换需要0.0008秒。向量化的GPU代码比CPU版本快13.55倍。向量化的GPU代码比未向量化的GPU版本快730.18倍。

解释

首先计算是旋转的计算。这涉及一个矩阵乘法,其转换为功能mtimes(*)。我们将其传递给pagefun随着相乘两套旋转:

Rel.R = pagefun(@mtimes,Robot.R”,Map.R);

Robot.R”是3×3矩阵,然后呢Map.R是一个3×3×N阵列。的pagefun函数从映射到相同的机器人旋转每个独立矩阵匹配,并且使我们所需要的3×3×N个输出。

翻译计算还涉及到一个矩阵乘法,但矩阵乘法的一般规则允许此来外循环没有任何变化。然而,这还涉及到减法Robot.TMap.T,这是不同的尺寸。由于这是一个元素,通过件工作,我们可以使用bsxfun投其所好尺寸的方法一样pagefun对于旋转做了:

Rel.T =机器人。R' * bsxfun(@minus, Map.T, Robot.T);

这次我们需要使用映射到函数的element-wise操作符-(-)。

更高级的GPU矢量化-解决“丢失的机器人”问题

如果我们的机器人是在地图上的一个未知的部分,它可能会使用一个全球性的搜索算法来定位自己。该算法将通过实施上述计算并寻找通过机器人的传感器和它所希望看到在该位置看到的物体之间良好的对应测试数个可能的位置。

现在,我们已经得到了多个机器人以及多个对象。N个对象和M机器人应该给我们N * M个转变了。为了区分“目标空间”“机器人空间”我们使用旋转的第四尺寸和用于翻译的第三。这意味着我们的机器人的旋转将是3×3×1-通过-M,和翻译将是3×1-通过-M。

我们用随机机器人位置初始化我们的搜索。一个好的搜索算法会使用拓扑或其他线索来更智能地进行搜索。

numRobots = 10;机器人= randomTransforms (numRobots);机器人。R= reshape(Robot.R, 3, 3, 1, []);%沿第4维展开Robot.T= reshape(Robot.T, 3, 1, []);%沿第三维展开gRobot。R= gpuArray(Robot.R); gRobot.T = gpuArray(Robot.T);

我们的新的循环转换功能需要两个嵌套的循环,在循环遍历机器人,以及超过的对象。

函数Rel = loopingTransform2(Robot, Map) Rel. r = zeros(3,3, numObjects, numRobots,'喜欢', Map.R);Rel.T = 0 (3, numObjects, numRobots,'喜欢', Map.T);我= 1:numObjectsj = 1: numRobots Rel.R (:,:, i, j) = Robot.R (:,:, 1, j) * Map.R(:,:我);Rel.T (:, i, j) =Robot.R(:,:,1,J)” *(Map.T(:,i)的 -  Robot.T(:,1,J));结束结束结束cpuTime = timeit(@()loopingTransform2(Robot, Map));流(“这需要%3.4f秒在CPU上执行%d变换。\ N”,cpuTime numObjects * numRobots);
在CPU上执行10000次转换需要0.1493秒。

我们使用GPU计时抽搐TOC这一次,因为否则计算时间会太长。这对于我们的目的来说已经足够精确了。为了确保包含与创建输出数据相关的任何成本,我们调用loopingTransform2只有一个输出变量,就像timeitgputimeit默认情况下做的。

流(“计算…\ n”);抽搐;gRel = loopingTransform2(gRobot, gMap);%#确定禁止未使用变量警告gpuTime = toc;流('在GPU上执行%3.4f转换需要%3.4f秒。\n',gpuTime numObjects * numRobots);流([“未向量化的GPU代码慢了%3.2f”,'而不是CPU版本。\n'), gpuTime / cpuTime);
计算……在GPU上执行10000次转换需要7.0564秒。未向量化的GPU代码比CPU版本慢47.26倍。

和以前一样,循环版本在GPU上运行速度要慢得多,因为它没有并行执行计算。

pagefun版本需要合并转置操作员以及mtimes进入到一个呼叫pagefun。我们还需要挤压转置机器人的方向把散布在机器人进入第三维,匹配的翻译。尽管这样,生成的代码是相当多的紧凑。

函数相对= pagefunTransform2(机器人,地图)RT = pagefun(@transpose,Robot.R);Rel.R = pagefun(@mtimes,RT,Map.R);Rel.T = pagefun(@mtimes,挤压(RT),bsxfun (@minus地图。T, Robot.T));结束

再一次,pagefunbsxfun适当地扩大规模。我们乘以3×3×1×m矩阵Rt用3×3×n乘1矩阵Map.R得到一个3×3×n×m的矩阵。

gpuPagefunTime = gputimeit(@()pagefunTransform2(gRobot, gMap));流([“这使用pagefun需要在GPU%3.4f秒”,'执行%d转换。\n', gpuPagefunTime numObjects * numRobots);流([向量化GPU代码快了%3.2f,'而不是CPU版本。\n'), cpuTime / gpuPagefunTime);流([向量化GPU代码快了%3.2f,'而不是未向量化的GPU版本。\n'), gpuTime / gpuPagefunTime);
这需要使用pagefun执行10000个变换的GPU0.0025秒。矢量GPU代码比CPU版本快59.97倍。矢量GPU代码比unvectorized GPU版本快2834.45倍。

结论

pagefun函数支持许多2d操万博1manbetx作,以及所支持的大多数标量操作arrayfunbsxfun。总之,这些功能使您向量化一系列涉及矩阵代数和数组操作的计算,消除了环路的需求,使巨大的性能提升。

无论你在循环中对GPU数据做小的计算,你应该考虑用这种方式转换成批处理实现。这也可以是一个机会,利用GPU来提高性能,在以前它没有提供性能增益。

结束
这个话题有用吗?