我只是在学习MATLAB,我发现很难理解循环与矢量化函数的性能因素。
在我之前的问题中:Nested for loops extremely slow in MATLAB (preallocated)我意识到使用矢量化函数与4个嵌套循环相比,运行时间差异
在该示例中,不是循环遍历4维数组的所有维度并计算每个向量的中值,而是调用中位数(stack,n)更清晰,更快,其中n表示中值函数的工作维度。
但中位数只是一个非常简单的例子而且我很幸运它实现了维度参数。
我的问题是 你如何自己编写一个函数,该函数与实现此维度范围的函数一样有效 ?
例如,您有一个函数my_median_1D
,它只适用于1-D向量并返回一个数字。
如何通过采用n维数组和“工作维度”参数来编写一个函数my_median_nD
,其作用类似于MATLAB的中位数?
更新
我找到了用于计算更高维度中位数的代码
% In all other cases, use linear indexing to determine exact location
% of medians. Use linear indices to extract medians, then reshape at
% end to appropriate size.
cumSize = cumprod(s);
total = cumSize(end); % Equivalent to NUMEL(x)
numMedians = total / nCompare;
numConseq = cumSize(dim - 1); % Number of consecutive indices
increment = cumSize(dim); % Gap between runs of indices
ixMedians = 1;
y = repmat(x(1),numMedians,1); % Preallocate appropriate type
% Nested FOR loop tracks down medians by their indices.
for seqIndex = 1:increment:total
for consIndex = half*numConseq:(half+1)*numConseq-1
absIndex = seqIndex + consIndex;
y(ixMedians) = x(absIndex);
ixMedians = ixMedians + 1;
end
end
% Average in second value if n is even
if 2*half == nCompare
ixMedians = 1;
for seqIndex = 1:increment:total
for consIndex = (half-1)*numConseq:half*numConseq-1
absIndex = seqIndex + consIndex;
y(ixMedians) = meanof(x(absIndex),y(ixMedians));
ixMedians = ixMedians + 1;
end
end
end
% Check last indices for NaN
ixMedians = 1;
for seqIndex = 1:increment:total
for consIndex = (nCompare-1)*numConseq:nCompare*numConseq-1
absIndex = seqIndex + consIndex;
if isnan(x(absIndex))
y(ixMedians) = NaN;
end
ixMedians = ixMedians + 1;
end
end
你能解释一下为什么这个代码与简单的嵌套循环相比如此有效?它具有嵌套循环,就像其他函数一样。
我不明白怎么可能快7倍?而且为什么这么复杂。
更新2
我意识到使用中位数不是一个很好的例子,因为它是一个复杂的功能本身需要对数组进行排序或其他巧妙的技巧。我用平均值重新进行了测试,结果更加疯狂: 19秒vs 0.12秒。 这意味着内置的sum方式比嵌套循环快160倍。
我很难理解行业领先的语言如何根据编程风格产生如此极端的性能差异,但我看到以下答案中提到的要点。
答案 0 :(得分:6)
更新2 (以解决您的更新问题)
MATLAB经过优化,可以很好地与数组配合使用。一旦你习惯了它,实际上非常好,只需输入一行并让MATLAB自己完成4D循环的全部工作,而不必担心它。 MATLAB通常用于原型设计/一次性计算,因此为编码人员节省时间并放弃C [++ |#]的一些灵活性是有意义的。
这就是为什么 MATLAB内部某些循环非常好 - 通常将它们编码为编译函数。
您提供的代码段并不真正包含执行主要工作的相关代码行,即
% Sort along given dimension
x = sort(x,dim);
换句话说,您显示的代码只需要通过现在排序的多维数组x
中的正确索引来访问中值(这不会花费太多时间)。访问所有数组元素的实际工作由sort
完成,这是一个内置(即编译和高度优化)的函数。
原始回答 (关于如何在数组上构建自己的快速函数)
实际上有很多内置广告采用维度参数:min(stack, [], n)
,max(stack, [], n)
,mean(stack, n)
,std(stack, [], n)
,median(stack,n)
,{{ 1}} ...以及sum(stack, n)
,exp()
等其他内置函数自动处理整个数组的每个元素(即sin()
自动执行四个嵌套循环的事实如果sin(stack)
是4D),您可以构建许多功能,您可能只需要依赖现有的内置功能。
如果这对于特定情况还不够,您应该查看repmat
,bsxfun
,arrayfun
和accumarray
这些功能非常强大的功能“ MATLAB方式“。只需在SO上搜索问题(或者更确切地说是答案)using one of these,我就这样学习了很多关于MATLABs的优点。
作为一个示例,假设你想沿维stack
实现堆栈的p-norm,你可以写
n
...您有效地重复使用function result=pnorm(stack, p, n)
result=sum(stack.^p,n)^(1/p);
的“which-dimension-capability”。
<强>更新强>
正如Max在评论中指出的那样,还要看看colon operator (:
)这是一个非常强大的工具,用于从数组中选择元素(甚至更改它的形状,更常见的是{{3} }})。
一般来说,请查看帮助中的reshape
部分 - 它包含sum
等。如上所述,还有repmat
以及一些比较模糊的辅助函数,你应该将它们用作构建块。
答案 1 :(得分:5)
你能解释一下,为什么这个代码与简单的嵌套循环相比如此有效?它具有嵌套循环,就像其他函数一样。
嵌套循环的问题不是嵌套循环本身。这是你在里面进行的操作。
每个函数调用(特别是对非内置函数)会产生一些开销;如果函数执行例如更多错误检查,无论输入大小如何,都需要相同的时间。因此,如果一个函数只有1毫秒的开销,如果你调用它1000次,你就会浪费一秒钟。如果您可以调用一次来执行矢量化计算,则只需支付一次开销。
此外,JIT compiler (pdf)可以帮助矢量化简单 for循环,例如,您只执行基本的算术运算。因此,在帖子中进行简单计算的循环会大量加速,而调用median
的循环则不会。
答案 2 :(得分:5)
除了已经说过的,你还应该理解vectorization涉及并行化,即对数据执行并发操作而不是顺序执行(想想SIMD指令),甚至利用线程和多处理器有些情况......
现在虽然已经讨论过“解释与编译”这一点,但没有人提到你可以通过编写MEX文件来扩展MATLAB,这些文件是用C编写的可编译可执行文件,可以直接从内部调用为普通函数MATLAB。这允许您使用较低级别的语言(如C。
)来实现性能关键部分最后,在尝试优化某些代码时,请始终记住MATLAB以列主顺序存储矩阵。与其他任意订单相比,按该顺序访问元素可以产生显着的改进。
例如,在您之前的关联问题中,您正在计算沿某个维度堆叠图像集的median
。现在订购这些尺寸的顺序极大地影响了性能。插图:
%# sequence of 10 images
fPath = fullfile(matlabroot,'toolbox','images','imdemos');
files = dir( fullfile(fPath,'AT3_1m4_*.tif') );
files = strcat(fPath,{filesep},{files.name}'); %'
I = imread( files{1} );
%# stacked images along the 1st dimension: [numImages H W RGB]
stack1 = zeros([numel(files) size(I) 3], class(I));
for i=1:numel(files)
I = imread( files{i} );
stack1(i,:,:,:) = repmat(I, [1 1 3]); %# grayscale to RGB
end
%# stacked images along the 4th dimension: [H W RGB numImages]
stack4 = permute(stack1, [2 3 4 1]);
%# compute median image from each of these two stacks
tic, m1 = squeeze( median(stack1,1) ); toc
tic, m4 = median(stack4,4); toc
isequal(m1,m4)
时差很大:
Elapsed time is 0.257551 seconds. %# stack1
Elapsed time is 17.405075 seconds. %# stack4
答案 3 :(得分:2)
在这种情况下
M = median(A,dim) returns the median values for elements along the dimension of A specified by scalar dim
但是使用常规功能,您可以尝试使用mat2cell
(可以使用n-D数组而不仅仅是矩阵)分割数组,并通过my_median_1D
应用cellfun
函数。下面我将使用median
作为示例来说明您获得了相同的结果,但您可以将其传递给m文件中定义的任何函数,或者使用@(args)
符号定义的匿名函数。 / p>
>> testarr = [[1 2 3]' [4 5 6]']
testarr =
1 4
2 5
3 6
>> median(testarr,2)
ans =
2.5000
3.5000
4.5000
>> shape = size(testarr)
shape =
3 2
>> cellfun(@median,mat2cell(testarr,repmat(1,1,shape(1)),[shape(2)]))
ans =
2.5000
3.5000
4.5000