提高matlab的效率

时间:2016-08-17 00:59:20

标签: performance matlab parfor

我有一个非常低效的matlab代码,我需要多次运行它。 代码基本上是一个很大的parfor loop,我想这几乎是不可能的。

代码首先加载几个参数和4-D矩阵,然后需要进行一些插值。所有这些都需要完成5000次(因此parfor循环)。

以下是代码的外观。我没有取出关键成分,就尽可能地简化了代码。

load file

nsim = 5000
T = 12; 
N = 1000;

cumQx = cumsum(Qx);
cumQz = cumsum(Qz);
cumQs = cumsum(Qs);

for k=1:nsim
st(k).ksim    = kstar*ones(N, T);
st(k).Vsim  = zeros(N,T);  
st(k).Psim = zeros(N,T);   
end


parfor k = 1:nsim    

    sysrand  = rand(T, 1);
    idiorand = rand(N, T);
    sigmarand = rand(T,1);

    xid = zeros(T, 1);
    zid = zeros(N, T);
    sid = zeros(T,1);
    xid(1) = 8;
    zid(:, 1) = 5;
    sid(1) = 1;
    % Initializing the simulation

    simx    = zeros(T,1);
    zsim    = ones(N,T)*zbar;
    simsx    = zeros(T,1);

    % Construct 3-D grid using 'ndgrid'
    [ks,zs] = ndgrid(kgrid,z);

    for j = 2:T
            sid(j) = find(cumQs(:, sid(j-1)) >= sigmarand(j), 1); 
            simsx(j-1) = sigmax(sid(j));

             xid(j) = find(cumQx(:, xid(j-1)) >= sysrand(j), 1); 
             simx(j-1) = x(xid(j));

             for n = 1:N
                 zid(n, j)   = find(cumQz(:, zid(n, j-1)) >= idiorand(n, j), 1); 
                 zsim(n,j-1) = z(zid(n, j));
             end
            st(k).ksim(:,j)      = interpn(ks, zs , squeeze(kprime(:,xid(j),:,sid(j))),   st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % K
            st(k).Vsim(:,j)      = interpn(ks, zs , squeeze(V(:,xid(j),:,sid(j))),        st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % V
            st(k).Psim(:,j)      = interpn(ks, zs , squeeze(P(:,xid(j),:,sid(j))),        st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % P


    end; 

end

以下是运行代码所需的矩阵的链接:http://www.filedropper.com/file_406

有没有更好的方法可以大大减少计算时间?我猜不是...... 理想情况下,可以采用矢量化k = 1:nsim循环的方法。

3 个答案:

答案 0 :(得分:6)

为了计算代码的时间和时间,我删除了parfor循环并用for循环替换它,然后使用MATLAB分析器。我使用nsims = 500进行测试。

使用分析器,我发现代码中存在两个关键瓶颈。第一个是最嵌套的for循环(n-loop)中的find()函数。第二个是对interpn()函数的三次调用。 这4行使用+ 88%的计算时间。 enter image description here

由于函数调用的开销(特别是考虑到它在嵌套循环中接收的调用数),以及内置的错误检查和find函数在这种情况下比理想的慢。管理。使用硬编码二进制搜索(下面)替换find函数可以极大地提高性能,而这只是替换了n循环中的find。对find使用nsims = 500会导致 29.8 秒的运行时间。使用二进制搜索,运行时间 12.1 秒。注意:这只能起作用,因为您的数据已排序,此代码无法替换每个实例中的查找。 编辑:在@EBH的其他答案中使用备用方法是一种更简洁的方法。

%perform binary search (same as your find function)
searchVal=idiorand(n, j);
il = 1;
iu = sizeCumQZ; % should be defined outside the loop as size(cumQz,1)
while (il+1<iu)
    lw=floor((il+iu)/2); % split the upper index
    if cumQz(lw,zid(n, j-1)) >= searchVal
        iu=lw; % decrease lower_index_b (whose x value remains \geq to lower bound)
    else
        il=lw; % increase lower_index_a (whose x value remains less than lower bound)
    end
end
if cumQz(il,zid(n, j-1))>=searchVal
    zid(n,j) = il;
else
    zid(n,j) = iu;
end

通过方法检查,错误管理和数据格式管理,interpn功能大大减慢了。标准interpn中使用的大约100行代码可以减少到每次调用2行,并且通过知道我们只需要一种类型的插值并且我们的数据符合特定格式来显着提高性能。为此,我们直接使用griddedInterpolant函数(见下文)。再次使用nsims = 500,使用interpn函数(仍使用未更改的find代码)的运行时间 29.8 秒。使用下面的改进方法,将运行时间减少到 20.4 秒。

精炼方法取代了interp的调用,此处显示为

st(k).ksim(:,j)      = interpn(ks, zs , squeeze(kprime(:,xid(j),:,sid(j))),   st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % K
st(k).Vsim(:,j)      = interpn(ks, zs , squeeze(V(:,xid(j),:,sid(j))),        st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % V
st(k).Psim(:,j)      = interpn(ks, zs , squeeze(P(:,xid(j),:,sid(j))),        st(k).ksim(:,j-1),zsim(:,j-1),'linear');       % P

更直接调用griddedInterpolant,如下所示:

F = griddedInterpolant(ks,zs,squeeze(kprime(:,xid(j),:,sid(j))), 'linear','none');
st(k).ksim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

F = griddedInterpolant(ks,zs,squeeze(V(:,xid(j),:,sid(j))), 'linear','none');
st(k).Vsim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

F = griddedInterpolant(ks,zs,squeeze(P(:,xid(j),:,sid(j))), 'linear','none');
st(k).Psim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

将二进制搜索而不是findgriddedInterpolant而不是interpn的调用相结合,可将总运行时间缩短至 3.8 秒,几乎提高了8倍在初始运行时间。

另外三点注意事项:

1)我建议遵循先前答案中概述的建议,特别是在可能的情况下远离结构并从循环中移动任何可能的东西(特别是ndgrid,因为这肯定只需要运行一次)。

2)我注意到,当使用nsims=5000时,此脚本中使用的总内存接近2.5gig。如果这接近系统的总可用内存,则可能导致显着减速。在这种情况下,我建议执行较小批量的计算,保存结果,然后继续进行进一步的计算。

3)最后,在我的测试中,使用parfor实际上比使用标准for循环慢。我相信这是因为在这种情况下每个单独的循环操作相对较短,并且与指定并行作业和管理集群工作程序相关的开销超过了并行处理的收益。如果您在大型计算机群集上,情况可能并非如此,但在我的单个(4核)计算机上,parfor仅导致速度变慢。我建议您根据自己的实际工作代码测试每个案例,如果您愿意,可以按照上面的建议进行更改。

我所做的完整代码更改如下所示,这些更改不包括对结构的使用或仍然建议的其他小优化的任何更改。

load file

tic;

nsim = 500
T = 12; 
N = 1000;

searchVal=1;
il = 1;
iu = 1;

cumQx = cumsum(Qx);
cumQz = cumsum(Qz);
cumQs = cumsum(Qs);

sizeCumQZ = size(cumQz,1);

for k=1:nsim
st(k).ksim    = kstar*ones(N, T);
st(k).Vsim  = zeros(N,T);  
st(k).Psim = zeros(N,T);   
end

%was parfor
for k = 1:nsim    

    sysrand  = rand(T, 1);
    idiorand = rand(N, T);
    sigmarand = rand(T,1);

    xid = zeros(T, 1);
    zid = zeros(N, T);
    sid = zeros(T,1);
    xid(1) = 8;
    zid(:, 1) = 5;
    sid(1) = 1;
    % Initializing the simulation

    simx    = zeros(T,1);
    zsim    = ones(N,T)*zbar;
    simsx    = zeros(T,1);

    % Construct 3-D grid using 'ndgrid'
    [ks,zs] = ndgrid(kgrid,z);

    for j = 2:T
            sid(j) = find(cumQs(:, sid(j-1)) >= sigmarand(j), 1); 
            simsx(j-1) = sigmax(sid(j));

             xid(j) = find(cumQx(:, xid(j-1)) >= sysrand(j), 1); 
             simx(j-1) = x(xid(j));

             for n = 1:N
                 %perform binary search (same as your find function)
                 searchVal=idiorand(n, j);
                 il = 1;
                 iu = sizeCumQZ;
                 while (il+1<iu)
                     lw=floor((il+iu)/2); % split the upper index
                     if cumQz(lw,zid(n, j-1)) >= searchVal
                         iu=lw; % decrease lower_index_b (whose x value remains \geq to lower bound)
                     else
                         il=lw; % increase lower_index_a (whose x value remains less than lower bound)
                     end
                 end
                 if cumQz(il,zid(n, j-1))>=searchVal
                     zid(n,j) = il;
                 else
                     zid(n,j) = iu;
                 end
                 zsim(n,j-1) = z(zid(n,j));
             end

             F = griddedInterpolant(ks,zs,squeeze(kprime(:,xid(j),:,sid(j))), 'linear','none');
             st(k).ksim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

             F = griddedInterpolant(ks,zs,squeeze(V(:,xid(j),:,sid(j))), 'linear','none');
             st(k).Vsim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

             F = griddedInterpolant(ks,zs,squeeze(P(:,xid(j),:,sid(j))), 'linear','none');
             st(k).Psim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));

    end;

end
toc;

编辑:通过griddedInterpolant更多地了解情况,通过网格连接将三个插值组合成一个,我能够提高15%的速度。 K,V和P插值的值。在代码的顶部,最好在循环外完成,我用:

替换原始网格创建
zRange = max(z(:))-min(z(:))+1;
zzzGrid = [z;z+1*zRange;z+2*zRange];% for K, V, and P 
[ksBig,zsBig] = ndgrid(kgrid,zzzGrid);
nZ = numel(z); %used below
valGrid = zeros(size(ksBig)); %used below

griddedInterpolant的3次调用替换为:

valGrid(:,1:nZ) = squeeze(kprime(:,xid(j),:,sid(j)));%K
valGrid(:,nZ+1:2*nZ) = squeeze(V(:,xid(j),:,sid(j)));%V
valGrid(:,2*nZ+1:3*nZ) = squeeze(P(:,xid(j),:,sid(j)));%P

F = griddedInterpolant(ksBig,zsBig,valGrid, 'linear','none');

st(k).ksim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1));
st(k).Vsim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1)+zRange);
st(k).Psim(:,j) = F(st(k).ksim(:,j-1),zsim(:,j-1)+2*zRange);

或者如果我们可以交易一个更复杂的设置,增加19%而不是增加15%,我们可以将griddedInterpolant移到j循环之外。在代码的开头,按如下方式设置网格:

zRange = max(z(:))-min(z(:))+1;
zzzGrid = [z;z+1*zRange;z+2*zRange];
zzzRange =  max(zzzGrid(:))-min(zzzGrid(:))+1;
zzzTGrid = [];
for j = 2:T
   zzzTGrid(end+1:end+numel(zzzGrid)) = zzzGrid+(j-2)*zzzRange;
end
[ksBig,zsBig] = ndgrid(kgrid,zzzTGrid);
nZ = numel(z); %used below
valGrid = zeros(size(ksBig)); %used below

并替换之前的griddedInterpolant,如下所示:

for j = 2:T
    %%%%%
    %...
    %Other code in the j loop
    %...
    %%%%%
    valGrid(:,(1:nZ)+3*nZ*(j-2)) = squeeze(kprime(:,xid(j),:,sid(j)));%K
    valGrid(:,(nZ+1:2*nZ)+3*nZ*(j-2)) = squeeze(V(:,xid(j),:,sid(j)));%V
    valGrid(:,(2*nZ+1:3*nZ)+3*nZ*(j-2)) = squeeze(P(:,xid(j),:,sid(j)));%P
end;

F = griddedInterpolant(ksBig,zsBig,valGrid, 'linear','none');

for j = 2:T
    st(k).ksim(:,j) = F(stTest(k).ksim(:,j-1),zsim(:,j-1)+3*zRange*(j-2));
    st(k).Vsim(:,j) = F(stTest(k).ksim(:,j-1),zsim(:,j-1)+zRange+3*zRange*(j-2));
    st(k).Psim(:,j) = F(stTest(k).ksim(:,j-1),zsim(:,j-1)+2*zRange+3*zRange*(j-2));
end

如果我们想要更加挑剔(增加2%的速度),我们可以将所有对squeeze的调用替换为不进行错误检查的版本,mySqueeze代替:

function b = mySqueeze(a)
%Trimmed down version of squeeze, a built-in MATLAB function, has no error-managment or case optimization
  siz = size(a);
  siz(siz==1) = []; % Remove singleton dimensions.
  siz = [siz ones(1,2-length(siz))]; % Make sure siz is at least 2-D
  b = reshape(a,siz);

答案 1 :(得分:4)

  1. 尽量不要使用结构体,这也会使您的代码更容易呈现矢量化(尽可能)。
  2. 在代码开头,替换:

    for k=1:nsim
        st(k).ksim = kstar*ones(N, T);
        st(k).Vsim = zeros(N,T);
        st(k).Psim = zeros(N,T);
    end
    

    使用:

    ksim = kstar*ones(N,T,nsim);
    Vsim = zeros(N,T,nsim);
    Psim = zeros(N,T,nsim);
    

    然后在最后而不是呼叫st,请致电:

    ksim(:,j,k) = interpn(ks,zs,squeeze(kprime(:,xid(j),:,sid(j))),ksim(:,j-1,k),zsim(:,j-1),'linear');
    Vsim(:,j,k) = interpn(ks,zs,squeeze(V(:,xid(j),:,sid(j))),     Vsim(:,j-1,k),zsim(:,j-1),'linear');
    Psim(:,j,k) = interpn(ks,zs,squeeze(P(:,xid(j),:,sid(j))),     Psim(:,j-1,k),zsim(:,j-1),'linear');
    

    如果需要,可以在循环结束后将所有三个变量放在结构中。

    1. 从循环中取出所有随机发生器。
    2. 在循环写入之前:

      sysrand = rand(T,nsim);
      idiorand = rand(N, T,nsim);
      sigmarand = rand(T,nsim);
      

      然后在循环内部替换:

      sigmarand(j) --> sigmarand(j,k)
      sysrand(j) --> sysrand(j,k)
      idiorand(n,j) --> idiorand(n,j,k)
      
      1. 向量化最内在的for循环:
      2. 取代:

        for n = 1:N
            zid(n,j)   = find(cumQz(:, zid(n, j-1)) >= idiorand(n,j), 1);
            zsim(n,j-1) = z(zid(n, j));
        end
        

        使用:

        cumQz_k = cumQz(:, zid(:, j-1)).';
        pos = bsxfun(@ge,cumQz_k,idiorand(:,j));
        zid(:,j) = (sum(~cumsum(pos'))+1).';
        zsim(:,j-1) = z(zid(:, j));
        

        这将为您提供高达90%的流程和60%的整体。所有其他条件相同(但不使用parfor),nsim = 200使用for循环的整个模拟需要 16s ,其中10个专用于此循环。矢量化后,模拟仅采用 6s ,从替代矢量化方法开始约为1秒。

        使用上面的所有评论,包括使用another answer中建议的griddedInterpolant,会在不到一分钟的时间内将您带到5000,因此最好不要使用parfor,由于开销通信(即使用大量内存),它可能会减慢速度。

        以下是最终代码(请注意,我将j替换为t):

        nsim = 5000;
        T = 12;
        N = 1000;
        cumQx = cumsum(Qx);
        cumQz = cumsum(Qz);
        cumQs = cumsum(Qs);
        ksim = kstar*ones(N,T,nsim);
        Vsim = zeros(N,T,nsim);
        Psim = zeros(N,T,nsim);
        sysrand = rand(T,nsim);
        idiorand = rand(N,T,nsim);
        sigmarand = rand(T,nsim);
        % Construct 3-D grid using 'ndgrid'
        [ks,zs] = ndgrid(kgrid,z);
        
        for k = 1:nsim
            % Initializing the simulation:
            xid = [8; zeros(T-1, 1)];
            zid = [ones(N,1)*5 zeros(N,T-1)];
            alter = [ones(N,1)*5 zeros(N,T-1)];
            sid = [1; zeros(T-1,1)];
            simx = zeros(T,1);
            zsim = ones(N,T)*zbar;
            simsx = zeros(T,1);
        
            for t = 2:T
                sid(t) = find(cumQs(:, sid(t-1)) >= sigmarand(t,k), 1);
                simsx(t-1) = sigmax(sid(t));
        
                xid(t) = find(cumQx(:, xid(t-1)) >= sysrand(t,k), 1);
                simx(t-1) = x(xid(t));
        
                cumQz_k = cumQz(:, zid(:, t-1)).';
                pos = bsxfun(@ge,cumQz_k,idiorand(:,t,k));
                zid(:,t) = (sum(~cumsum(pos'))+1).';
                zsim(:,t-1) = z(zid(:, t));
        
                F = griddedInterpolant(ks,zs,squeeze(kprime(:,xid(t),:,sid(t))), 'linear','none');
                ksim(:,t,k) = F(ksim(:,t-1,k),zsim(:,t-1));
                F = griddedInterpolant(ks,zs,squeeze(V(:,xid(t),:,sid(t))), 'linear','none');
                Vsim(:,t,k) = F(ksim(:,t-1,k),zsim(:,t-1));
                F = griddedInterpolant(ks,zs,squeeze(P(:,xid(t),:,sid(t))), 'linear','none');
                Psim(:,t,k) = F(ksim(:,t-1,k),zsim(:,t-1));
        
            end
        end
        

        欢迎您在评论中询问代码中任何不清楚的部分。

答案 2 :(得分:0)

除了更高级别的优化(代码似乎可以向量化,但我不知道自己不运行它),你在循环中做了很多不必要的计算/内存重新分配。

}}?如果它们具有垃圾值或零,则不会真正受到伤害,因为您每次只访问当前索引。更不用说,你可能会因为没有归零而节省了大量的内存分配操作。

此外,没有必要重新计算ndgrid。

将所有这些东西移出你的圈子。