为什么bsxfun在gpuArray上这么慢?

yur*_*ero 1 performance matlab 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);
Run Code Online (Sandbox Code Playgroud)

我分别获得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);
Run Code Online (Sandbox Code Playgroud)

(差异:for - > parfor).我得到2.7(秒)和6.3(秒).

为什么两种情况下GPU的接近速度都要慢?在我的工作中,myfunc要复杂得多.我已经定义它以便它与非GPU一起运行良好bsxfun但是当我像上面所做的GPU-ize时,我遇到了错误Use of functional workspace is not supported.(在我的工作中,myfunc是在内部和parfor循环开始时定义的.)你能不能请还解释了这个错误表明的是什么?

Dev*_*-iL 5

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


以下列出了可以改进的内容:

1.随机数生成:

生成随机数在GPU上效率更高,更不用说它可以节省昂贵的通信开销.MATLAB为我们提供了几种在GPU上建立阵列的便捷功能.换一种说法,

A = randn(1000,1);
gA = gpuArray(A);
Run Code Online (Sandbox Code Playgroud)

可以替换为:

gA = gpuArray.randn(1000,1);
Run Code Online (Sandbox Code Playgroud)

2.重新定义现有职能bsxfun:

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

myfunc = @(a,b)(a.*b);
...
bsxfun(myfunc,A,B');
Run Code Online (Sandbox Code Playgroud)

有:

bsxfun(@times,A,B.');
Run Code Online (Sandbox Code Playgroud)

(或在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 = ...
Run Code Online (Sandbox Code Playgroud)

3.使用ctranspose而不是transpose:

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

4.定时功能执行:

长话短说:tic&toc是不是一个很好的迹象通常使用timeit来代替.

5.隐式创建并行池:

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

6.比较苹果和苹果:

这两行代码的工作量不同:

C = bsxfun(myfunc,A,B');
C = gather(bsxfun(myfunc,gA,gB'));
Run Code Online (Sandbox Code Playgroud)

为什么?因为第二版还必须将bsxfunGPU内存的结果传输到RAM - 这不是免费的(就运行时而言).在本示例中,这意味着您要为每次迭代添加~800KB数据的传输.我假设你的实际问题有更大的矩阵,所以你明白这个开销变得非常快.

7.保留您不需要的变量:

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

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

你可以做:

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

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