为什么bsxfun在gpuArray上这么慢?

时间:2016-12-30 06:30:48

标签: matlab performance gpgpu parfor bsxfun

我有一台运行在Win 10机器上的MATLAB 2016a,库存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);

(差异:for - > parfor)。我绕过 2.7(秒)和6.3(秒)

为什么GPU在这两种情况下都会变慢?在我的工作中,myfunc要复杂得多。我已经定义了它,以便它与非GPU bsxfun一起使用,但是当我像上面所做的那样GPU-ize时,我遇到了错误Use of functional workspace is not supported.(在我的工作中,myfuncparfor循环的内部和开头定义。)您能解释一下这个错误的含义吗?

2 个答案:

答案 0 :(得分:5)

首先让我说GPU并不是一些可以某种方式提高计算速度的神奇对象。它们是一种对某些工作有益的工具,它们有一些需要考虑的限制。 GPU的经验法则是数学运算比内存访问“更便宜”,因此,例如,如果每次需要时重新计算某些数组而不是将其保存到临时变量并访问它,那么为GPU编写的代码可能运行得更好。 。底线 - GPU编码需要一些不同的思考,这些都超出了当前答案的范围。

以下列出了可以改进的事项:

1。随机数生成:

生成随机数在GPU上效率更高,更不用说它可以节省昂贵的通信开销。 MATLAB为我们提供了several convenience functions to establish arrays on a GPU。换句话说,

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

可以替换为:

gA = gpuArray.randn(1000,1);

2。重新定义bsxfun

的现有功能

没有必要这样做。看看the list of builtin functions supported by bsxfun.*times已经是其中之一!因此,您可以替换:

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

使用:

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

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

此外,在脚本文件中将自定义函数定义为nested function并使用@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。使用ctranspose代替transpose

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

4。定时功能执行:

长话短说:tic& toc通常不是一个好的指示,而是使用timeit

5。隐式创建并行池:

这是一个相当小的评论:在2 nd 代码段中,您先使用parfor而不先调用parpool。这意味着如果未在该阶段创建池,则创建时间(几秒)将添加到tic / toc报告的时间。为避免这种情况,请遵循"Explicit is better than implicit"的编程原则,并事先调用parpool

6。比较苹果和苹果:

这两行代码不会做同样的工作

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

为什么?因为2 nd 版本还必须将bsxfun的结果从GPU内存传输到RAM - 这不是 free (就运行时而言)。在本示例中,这意味着您要为每次迭代添加~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迭代中定义一个函数。也许这些帖子可以提供帮助:12

答案 1 :(得分:0)

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

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

>> gputimeit(@()gA.*gB')
ans = 
    2.6918e-04

>> gputimeit(@()gather(gA.*gB'))
ans =
    0.0011

因此,与实际计算的0.3ms成本相比,10万个元素(0.8MB)的数据传输是11毫秒。 (请注意,我没有使用bsxfun,因为MATLAB R2016b为逐个元素的操作进行了自动尺寸扩展,从而无需使用它。)

除非您需要显示它或将其写入磁盘(或在其上运行不支持GPU计算的操作),否则您永远不需要收集gpuArray,所以不要,把它留在设备上。 CPU不会受到此数据传输问题的影​​响,因此相比之下它看起来很不错。

另一点是,GeForce显卡具有双倍精度的糟糕性能 - 例如,GTX 970的单精度报告性能为3494 GFlops,而双精度则为99 GFlops。它们针对您看到的显示进行了优化。但是,在这种情况下,切换到单精度会产生很大的不同,因为操作首先绑定数据传输,然后绑定内存带宽。计算时间并没有真正进入它。

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