为什么 gpuArray 上的 bsxfun 这么慢?
Why is bsxfun on a gpuArray so slow?
我在配备 i5 2500K 和 2 GTX 970 的 Win 10 机器上安装了 MATLAB 2016a 运行ning。我是 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 化时,我遇到了错误 Use of functional workspace is not supported.
(在我的工作中,myfunc
在 parfor
循环的内部和开始处定义。)您能否解释一下这个错误的含义?
首先让我说 GPU 不是某种可以以某种方式提高任何计算速度的神奇物体。它们是适合某些工作的工具,但也有需要考虑的局限性。 GPU 的经验法则是数学运算 "cheaper" 而不是内存访问,因此例如,如果每次需要时重新计算某个数组而不是将其保存到,为 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 instead of transpose:
这里解释的很好。长话短说:您应该养成使用 .'
进行转置和使用 '
进行复共轭转置的习惯。
4。计时功能执行:
长话短说:tic
和 toc
通常不是一个好的指示,请改用 timeit
。
5。隐式创建并行池:
这是一个相当小的评论:在第 2nd 代码片段中,您使用了 parfor
而没有调用 parpool
first. This means that if the pool is not created at that stage, the creation time (of several seconds) will be added to the timing reported by tic
/toc
. To avoid this, follow the programming principle of "Explicit is better than implicit" 并预先调用 parpool
。
6.同类比较:
这两行代码做的工作量不同:
C = bsxfun(myfunc,A,B');
C = gather(bsxfun(myfunc,gA,gB'));
这是为什么?因为 2nd 版本还必须将 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
迭代中定义一个函数。也许这些帖子可以提供帮助:1, 2.
我 运行 你的代码在我的台式机上使用 Tesla K20 和 Quadro K620 以及我的笔记本电脑上使用 GTX 9XXM 芯片。在这两种情况下,我都没有得到像你的第一组数字那样糟糕的东西,而且两张 GeForce 卡(好吧,你的卡和我的芯片)的统计数据并没有表明如此大的差异。也许您的第一组时间安排因某些开销而出现偏差?当然,您的第二组计时似乎可以证明这一点,因为时间提高了太多。
不过,我可以回答一般问题。你为什么打电话给 gather
?大部分成本在数据t运行sfer, cf:
>> gputimeit(@()gA.*gB')
ans =
2.6918e-04
>> gputimeit(@()gather(gA.*gB'))
ans =
0.0011
因此,与实际计算的 0.3 毫秒成本相比,10 万个元素 (0.8MB) 的数据 t运行sfer 为 11 毫秒。 (请注意,我没有使用 bsxfun
,因为 MATLAB R2016b 会自动对 element-wise 操作进行维度扩展,从而消除了对它的需求。)
除非你需要显示它或将它写入磁盘(或运行不支持GPU计算的操作),你永远不需要收集gpuArray
,所以不要',将其留在设备上。 CPU 并没有受到这个数据 t运行sfer 问题的影响,所以当然相比之下它看起来不错。
另一点是 GeForce 卡的双精度性能很糟糕 - 例如,您的 GTX 970 的单精度性能为 3494 GFlops,而双精度性能仅为 109 GFlops。它们针对您看到的显示进行了优化。但是,在这种情况下切换到单精度不会有太大区别,因为操作首先是数据 t运行sfer 绑定,然后是内存 bandwidth-bound。计算时间并没有真正考虑在内。
就并行化而言,我质疑您的数字,因为改进对于仅两个 GPU 来说太好了。但是,您有两个 GPU,因此它们可以并行处理数据 t运行sfer(和计算),因此您可以获得改进。不过,这似乎不足以抵消您的开销。
我在配备 i5 2500K 和 2 GTX 970 的 Win 10 机器上安装了 MATLAB 2016a 运行ning。我是 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 化时,我遇到了错误 Use of functional workspace is not supported.
(在我的工作中,myfunc
在 parfor
循环的内部和开始处定义。)您能否解释一下这个错误的含义?
首先让我说 GPU 不是某种可以以某种方式提高任何计算速度的神奇物体。它们是适合某些工作的工具,但也有需要考虑的局限性。 GPU 的经验法则是数学运算 "cheaper" 而不是内存访问,因此例如,如果每次需要时重新计算某个数组而不是将其保存到,为 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 instead of transpose:
这里解释的很好。长话短说:您应该养成使用 .'
进行转置和使用 '
进行复共轭转置的习惯。
4。计时功能执行:
长话短说:tic
和 toc
通常不是一个好的指示,请改用 timeit
。
5。隐式创建并行池:
这是一个相当小的评论:在第 2nd 代码片段中,您使用了 parfor
而没有调用 parpool
first. This means that if the pool is not created at that stage, the creation time (of several seconds) will be added to the timing reported by tic
/toc
. To avoid this, follow the programming principle of "Explicit is better than implicit" 并预先调用 parpool
。
6.同类比较:
这两行代码做的工作量不同:
C = bsxfun(myfunc,A,B');
C = gather(bsxfun(myfunc,gA,gB'));
这是为什么?因为 2nd 版本还必须将 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
迭代中定义一个函数。也许这些帖子可以提供帮助:1, 2.
我 运行 你的代码在我的台式机上使用 Tesla K20 和 Quadro K620 以及我的笔记本电脑上使用 GTX 9XXM 芯片。在这两种情况下,我都没有得到像你的第一组数字那样糟糕的东西,而且两张 GeForce 卡(好吧,你的卡和我的芯片)的统计数据并没有表明如此大的差异。也许您的第一组时间安排因某些开销而出现偏差?当然,您的第二组计时似乎可以证明这一点,因为时间提高了太多。
不过,我可以回答一般问题。你为什么打电话给 gather
?大部分成本在数据t运行sfer, cf:
>> gputimeit(@()gA.*gB') ans = 2.6918e-04 >> gputimeit(@()gather(gA.*gB')) ans = 0.0011
因此,与实际计算的 0.3 毫秒成本相比,10 万个元素 (0.8MB) 的数据 t运行sfer 为 11 毫秒。 (请注意,我没有使用 bsxfun
,因为 MATLAB R2016b 会自动对 element-wise 操作进行维度扩展,从而消除了对它的需求。)
除非你需要显示它或将它写入磁盘(或运行不支持GPU计算的操作),你永远不需要收集gpuArray
,所以不要',将其留在设备上。 CPU 并没有受到这个数据 t运行sfer 问题的影响,所以当然相比之下它看起来不错。
另一点是 GeForce 卡的双精度性能很糟糕 - 例如,您的 GTX 970 的单精度性能为 3494 GFlops,而双精度性能仅为 109 GFlops。它们针对您看到的显示进行了优化。但是,在这种情况下切换到单精度不会有太大区别,因为操作首先是数据 t运行sfer 绑定,然后是内存 bandwidth-bound。计算时间并没有真正考虑在内。
就并行化而言,我质疑您的数字,因为改进对于仅两个 GPU 来说太好了。但是,您有两个 GPU,因此它们可以并行处理数据 t运行sfer(和计算),因此您可以获得改进。不过,这似乎不足以抵消您的开销。