下标索引和线性索引之间的性能差异

时间:2016-01-12 00:43:23

标签: matlab indexing

我在MATLAB中有一个2D矩阵,我使用两种不同的方式来访问它的元素。一个基于下标索引,另一个基于线性索引。我通过以下代码测试这两种方法:

N = 512; it = 400; im = zeros(N);
%// linear indexing
[ind_x,ind_y] = ndgrid(1:2:N,1:2:N);
index = sub2ind(size(im),ind_x,ind_y);

tic
for i=1:it
    im(index) = im(index) + 1;
end
toc %//cost 0.45 seconds on my machine (MATLAB2015b, Thinkpad T410)

%// subscript indexing
x = 1:2:N;
y = 1:2:N;

tic
for i=1:it
    im(x,y) = im(x,y) +1;
end
toc %// cost 0.12 seconds on my machine(MATLAB2015b, Thinkpad T410)

%//someone pointed that double or uint32 might an issue, so we turn both into uint32

%//uint32 for linear indexing
index = uint32(index);
tic
for i=1:it
    im(index) = im(index) +1;
end
toc%// cost 0.25 seconds on my machine(MATLAB2015b, Thinkpad T410)

%//uint32 for the subscript indexing
x = uint32(1:2:N);
y = uint32(1:2:N);
tic
for i=1:it
    im(x,y) = im(x,y) +1;
end
toc%// cost 0.11 seconds on my machine(MATLAB2015b, Thinkpad T410)

%% /*********************comparison with others*****************/
%//third way of indexing, loops
tic
for i=1:it
    for j=1:2:N
        for k=1:2:N
            im(j,k) = im(j,k)+1;
        end
    end
end
toc%// cost 0.74 seconds on my machine(MATLAB2015b, Thinkpad T410)

直接使用下标索引似乎比从sub2ind获得的线性索引更快。有谁知道为什么?我以为它们几乎一样。

4 个答案:

答案 0 :(得分:8)

直觉

正如Daniel在answer中提到的,线性索引在RAM中占用更多空间,而下标则更小。

对于下标索引,在内部,Matlab不会创建线性索引,但它会使用(双)编译的循环遍历所有元素。

另一方面,下标版本必须循环遍历从外部传递的所有线性索引,这将需要更多内存读取,因此需要更长时间。

权利要求

  • 线性索引更快
  • ...只要索引的总数相同

计时

从时间上我们看到第一个索赔的直接确认,我们可以通过一些额外的测试推断第二个索引(下面)。

LOOPED
      subs assignment: 0.2878s
    linear assignment: 0.0812s

VECTORIZED
      subs assignment: 0.0302s
    linear assignment: 0.0862s

第一项索赔

我们可以用循环测试它。 subref操作的数量是相同,但线性索引直接指向感兴趣的元素,而内部的下标需要转换。

感兴趣的功能:

function B = subscriptedIndexing(A,row,col)
n = numel(row);
B = zeros(n);
for r = 1:n
    for c = 1:n
        B(r,c) = A(row(r),col(c));
    end
end
end

function B = linearIndexing(A,index)
B = zeros(size(index));
for ii = 1:numel(index)
    B(ii) = A(index(ii));
end
end

第二项索赔

该声明是使用矢量化方法时观察到的速度差异的推论。

首先,矢量化方法(与循环相反)加速了下标分配,而线性索引稍微慢一点(可能没有统计意义)。

其次,两种索引方法的唯一区别来自索引/下标的大小。我们希望将此作为唯一可能导致时间差异的原因。另一个主要参与者可能是JIT优化。

测试功能:

function B = subscriptedIndexingVect(A,row,col)
n = numel(row);
B = zeros(n);
B = A(row,col);
end

function B = linearIndexingVect(A,index)
B = zeros(size(index));
B = A(index);
end

注意 :我保留了B的多余预分配,以保持矢量化和循环方法的可比性。换句话说,时间上的差异应该只来自索引和循环的内部实现。

所有测试均以:

运行
function testFun(N)
A             = magic(N);
row           = 1:2:N;
col           = 1:2:N;
[ind_x,ind_y] = ndgrid(row,col);
index         = sub2ind(size(A),ind_x,ind_y);

% isequal(linearIndexing(A,index), subscriptedIndexing(A,row,col))
% isequal(linearIndexingVect(A,index), subscriptedIndexingVect(A,row,col))

fprintf('<strong>LOOPED</strong>\n')
fprintf('      subs assignment: %.4fs\n',  timeit(@()subscriptedIndexing(A,row,col)))
fprintf('    linear assignment: %.4fs\n\n',timeit(@()linearIndexing(A,index)))
fprintf('<strong>VECTORIZED</strong>\n')
fprintf('      subs assignment: %.4fs\n',  timeit(@()subscriptedIndexingVect(A,row,col)))
fprintf('    linear assignment: %.4fs\n',  timeit(@()linearIndexingVect(A,index)))
end

打开/关闭JIT有 NO 影响:

feature accel off
testFun(5e3)
...

VECTORIZED
      subs assignment: 0.0303s
    linear assignment: 0.0873s

feature accel on
testFun(5e3)
...

VECTORIZED
      subs assignment: 0.0303s
    linear assignment: 0.0871s

排除了,下标任务的优越速度来自JIT优化,这使我们得出唯一可信的原因, RAM访问次数。确实,最终矩阵具有相同数量的元素。但是,线性赋值必须检索索引的所有元素才能获取数字。

SETUP

使用MATLAB R2015b在Win7 64上测试。由于Matlab's execution engine

的最新变化,Matlab的早期版本将提供不同的结果

事实上,在Matlab R2014a 中关闭JIT会影响时间,但仅限于循环(预期结果):

feature accel off
testFun(5e3)

LOOPED
      subs assignment: 7.8915s
    linear assignment: 6.4418s

VECTORIZED
      subs assignment: 0.0295s
    linear assignment: 0.0878s

这再次证实了线性和sibscripted赋值之间的时序差异应该来自RAM访问的数量,因为JIT在矢量化方法中不起作用。

答案 1 :(得分:4)

下面的索引编写速度要快得多,这并不让我感到惊讶。如果您查看输入数据,则在这种情况下索引要小得多。对于下标索引案例,您有512个元素;而对于线性索引案例,您有65536个元素。

当您将示例应用于向量时,您会注意到两种方法之间没有区别。

以下是我用来评估不同矩阵大小的略微修改的代码:

it = 400; im = zeros(512*512,1);
x = 1:2:size(im,1);
y = 1:2:size(im,2);
%// linear indexing
[ind_x,ind_y] = ndgrid(x,y);
index = sub2ind(size(im),ind_x,ind_y);

tic
for i=1:it
    im(index) = im(index) + 1;
end
toc 

%// subscript indexing


tic
for i=1:it
    im(x,y) = im(x,y) +1;
end
toc 

答案 2 :(得分:0)

一个非常好的问题。在前面,我不知道正确的答案,但是,你可以分析行为。将第一个toc保存到t1,将第二个toc保存到t2。最后计算t1/t2。你会发现,改变迭代次数或矩阵的大小(几乎)不会改变因子。 我建议:

  • 迭代次数只会提高tictoc的质量。 (明显?)
  • 矩阵的大小没有影响,即语法中必须有时间延迟。

我想,只需要从线性索引到下标索引的内部检查或转换,即您执行的内部添加(操作)完全相同。使用下标索引而不是线性索引似乎更自然,因此mathworks可能只是优化了第一个索引。

<强>更新: 您还可以简单地访问矩阵中的元素,使用下标索引比使用线性索引更快。这支持了理论,即从线性到下标内部完成转换。

答案 3 :(得分:0)

免责声明:我目前没有MATLAB许可证,因此我在下面提供的代码未经测试。但是,如果有人决定进行测试,请相应地评论此答案。

根据您的MATLAB版本(您使用的是R2015b吗?),在调用&#34;零&#34;时,您可能没有支付预分配的全部前期费用。您有可能在第一次获取/设置 im 时支付分配费用,这会在您首次访问 im 中的值时导致额外但隐藏的开销。< / p>

请参阅:http://undocumentedmatlab.com/blog/preallocation-performance

作为初步测试,我建议切换您正在分析代码的顺序:

N = 512; it = 400; im = zeros(N);

%// subscript indexing
x = 1:2:N;
y = 1:2:N;

tic
for i=1:it
    im(x,y) = im(x,y) +1;
end
toc %// What's the cost now?

%// linear indexing
[ind_x,ind_y] = ndgrid(1:2:N,1:2:N);
index = sub2ind(size(im),ind_x,ind_y);

tic
for i=1:it
    im(index) = im(index) + 1;
end
toc %// What's the cost now?

为了对下标与线性索引进行更公平的分析,我建议使用两种可能的方法之一:

  1. 确保通过创建两个单独的im矩阵( im1 im2 )来确定两种方法的分配成本,两者最初都设置为零(N),并使用每个矩阵用于单独的索引方法。
  2. 在对下标与线性索引进行实际分析之前,对im的每个元素运行完整的get / set。
  3. 方法1:

    N = 512; it = 400; im1 = zeros(N); im2 = zeros(N);
    
    %// subscript indexing
    x = 1:2:N;
    y = 1:2:N;
    
    tic
    for i=1:it
        im1(x,y) = im1(x,y) + 1;
    end
    toc %// What's the cost now?
    
    %// linear indexing
    [ind_x,ind_y] = ndgrid(1:2:N,1:2:N);
    index = sub2ind(size(im2),ind_x,ind_y);
    
    tic
    for i=1:it
        im2(index) = im2(index) + 1;
    end
    toc %// What's the cost now?
    

    方法2:

    N = 512; it = 400; im = zeros(N);
    
    %// Run a full get/set on each element to force allocation
    tic
    for i=1:N^2
        im(i) = im(i) +1;
    end
    toc 
    
    
    %// subscript indexing
    x = 1:2:N;
    y = 1:2:N;
    
    tic
    for i=1:it
        im(x,y) = im(x,y) +1;
    end
    toc %// What's the cost now?
    
    %// linear indexing
    [ind_x,ind_y] = ndgrid(1:2:N,1:2:N);
    index = sub2ind(size(im),ind_x,ind_y);
    
    tic
    for i=1:it
        im(index) = im(index) + 1;
    end
    toc %// What's the cost now?
    

    我有第二个假设,即当你明确声明要访问的每个元素时,你会产生一些额外的开销,而不是你有MATLAB为你推断元素。 excasa的"duplicate post" reference(在我的拙见中并不完全重复)具有相同的一般见解,但使用不同的数据点来得出这个结论。我不会在这里写这个例子,但基本上,创建一个直接的巨型数组索引与较小的下标索引 x y 为MATLAB提供了更少的内部优化空间。我不知道MATLAB内部会执行这些特定的优化,但也许它们来自你可能知道为MATLAB's JIT/LXE的黑魔法。如果你真的想检查JIT是否是这里的罪魁祸首(并且在2014b或之前工作),那么你可以尝试禁用它然后运行上面的代码。

    有几种方法可以禁用JIT:

    1. 使用undocumented feature methods
    2. 将命令复制/粘贴到命令提示符,而不是直接从脚本编辑器运行它们。
    3. 不幸的是,我不知道在R2015a及更高版本中转向LXE的方法,并试图诊断LXE是否是罪魁祸首可能是一场艰苦的战斗。如果您遇到困难,也许可以通过MathWorks' technical supportMathWorks Central进一步深入研究。您可能会惊讶地发现来自任何一个来源的一些令人震惊的专家。