为什么 gpuArray 上的 bsxfun 这么慢



我让 MATLAB 2016a 在一台 Win 10 机器上运行,库存为 i5 2500K 和 2 GTX 970。我是 GPU 计算的新手,我正在探索如何使用我的 GPU 加快我的计算速度。

所以我运行以下简单的代码:

clear;
A = randn(1000,1);
B = randn(100,1);
n = 10000;
gA = gpuArray(A);
gB = gpuArray(B);
myfunc = @(a,b)(a.*b);
tic;
for i = 1:n
C = bsxfun(myfunc,A,B');
end
disp(toc);
tic;
for i = 1:n
C = gather(bsxfun(myfunc,gA,gB'));
end
disp(toc);

我分别得到8.2(秒)和 321.3864(秒)。

clear;
A = randn(1000,1);
B = randn(100,1);
n = 10000;
gA = gpuArray(A);
gB = gpuArray(B);
myfunc = @(a,b)(a.*b);
tic;
parfor i = 1:n
C = bsxfun(myfunc,A,B');
end
disp(toc);
tic;
parfor i = 1:n
C = gather(bsxfun(myfunc,gA,gB'));
end
disp(toc);

(区别:对于 --> parfor)。我得到大约 2.7(秒)和 6.3(秒)。

请问为什么 GPU 方法在这两种情况下都慢?在我的工作中,myfunc要复杂得多。我已经定义了它,以便它与非 GPUbsxfun配合良好,但是当我像上面所做的那样进行 GPU 化时,我遇到了错误Use of functional workspace is not supported.(在我的工作中,myfunc是在parfor循环的内部和开头定义的。您能否也解释一下此错误表示什么?

首先我要说的是,GPU不是什么神奇的物体,可以以某种方式提高任何计算的速度。它们是适用于某些工作的工具,并且需要考虑它们的局限性。GPU 的经验法则是数学运算比内存访问"便宜",因此,例如,如果每次需要时重新计算某个数组,而不是将其保存到临时变量并访问它,则为 GPU 编写的代码可能会运行得更好。底线 - GPU 编码需要一点不同的思维,这些东西超出了本答案的范围。


以下是可以改进的事项列表:

1. 随机数生成:

在 GPU 上生成随机数的效率要高得多,更不用说它可以为您节省昂贵的通信开销。MATLAB 为我们提供了几个方便的函数来在 GPU 上建立数组。换句话说,

A = randn(1000,1);
gA = gpuArray(A);

可以替换为:

gA = gpuArray.randn(1000,1);

2. 重新定义bsxfun的现有功能:

没有必要这样做。看看bsxfun支持的内置函数列表:.*times已经是其中之一了!因此,您可以替换:

myfunc = @(a,b)(a.*b);
...
bsxfun(myfunc,A,B');

跟:

bsxfun(@times,A,B.');

(或在 MATLAB 版本中>= R2016b:A.*B.')。

此外,将自定义函数定义为脚本文件中的嵌套函数并使用@myFunc调用它会更好

,即:
function main
...
bsxfun(@myFunc,A,B')
% later in the same file, or in a completely different one:
function out = myFunc(a,b)
out = ...

3. 使用转置代替转置:

这在这里解释得很好。长话短说:你应该养成使用.'进行转置的习惯,'用于复共轭转置。

4. 定时函数执行:

长话短说:tictoc通常不是一个好的迹象,改用timeit

5. 隐式创建并行池:

这是一个相当小的注释:在第二个代码片段中,您使用parfor而不先调用parpool。这意味着,如果在该阶段未创建池,则创建时间(几秒钟)将添加到tic/toc报告的时间中。为了避免这种情况,请遵循"显式优于隐式"的编程原则,并事先调用parpool

6. 比较苹果和苹果:

这两行代码不执行相同的工作量

C = bsxfun(myfunc,A,B');
C = gather(bsxfun(myfunc,gA,gB'));

这是为什么呢?因为第 2还必须将bsxfun的结果从 GPU 内存传输到 RAM - 这不是免费的(就运行时而言)。在本例中,这意味着您要向每次迭代添加 ~800KB 的数据传输。我假设您的实际问题具有更大的矩阵,因此您了解此开销很快就会变得严重。

7. 保留不需要的变量:

另一个小评论:而不是做:

parfor i = 1:n % or "for"
C = bsxfun(myfunc,A,B');
end

你可以做:

parfor i = 1:n % or "for"
[~] = bsxfun(myfunc,A,B');
end

至于错误,我无法在我的 R2016b 上重现它,但这听起来像是与捕获不兼容(即创建匿名函数时使用的变量快照的机制)与parfor所需的切片不兼容有关的问题。我不知道你到底做错了什么,但在我看来,你不应该在parfor迭代中定义一个函数。也许这些帖子可以帮助:1,2。

我在桌面上使用Tesla K20和Quadro K620以及带有GTX 9XXM芯片的笔记本电脑运行您的代码。在这两种情况下,我都没有得到任何像你的第一组数字一样糟糕的东西,而且两张 GeForce 卡(嗯,你的卡和我的芯片)的统计数据并没有表明如此大的差异。也许你的第一组时间被一些开销扭曲了?当然,你的第二组时间似乎证明了这一点,因为时间改善了太多。

不过,我可以回答一般问题。你为什么要打电话给gather?大部分成本是在数据传输中,参见:

>> gputimeit(@()gA.*gB') 答 = 2.6918e-04>> gputimeit(@()gather(gA.*gB')) 答 = 0.0011

因此,10万个元素(0.8MB)的数据传输是11毫秒,而实际计算的成本为0.3毫秒。(请注意,我没有使用bsxfun因为 MATLAB R2016b 会为元素级操作执行自动维度扩展,从而消除了对它的需要。

除非您需要显示它或将其写入磁盘(或对其运行不支持 GPU 计算的操作),否则您永远不需要收集gpuArray,所以不要将其留在设备上。CPU没有受到这种数据传输问题的阻碍,所以相比之下当然看起来不错。

另一点是,GeForce 卡在双精度方面的性能很差——例如,您的 GTX 970 报告的单精度性能为 3494 GFlops,但双精度的性能为 109 GFlops。它们针对您看到的显示进行了优化。但是,在这种情况下,切换到单精度不会有太大区别,因为操作首先是数据传输绑定,其次是内存带宽限制。计算时间并没有真正进入其中。

就并行化而言,我质疑您的数字,因为对于仅两个 GPU 来说,改进太好了。但是,您有两个GPU,因此它们可以并行进行数据传输(和计算),因此您可以获得改进。不过,似乎不足以抵消您情况下的开销。

相关内容

  • 没有找到相关文章

最新更新