繁体   English   中英

向量化代码 - 如何减少 MATLAB 计算时间

[英]Vectorizing code - How to reduce MATLAB computational time

我有这段代码

N=10^4;
for i = 1:N
    [E,X,T] = fffun(); % Stochastic simulation. Returns every time three different vectors (whose length is 10^3).
    X_(i,:)=X;
    T_(i,:)=T;
    GRID=[GRID T];
end
GRID=unique(GRID);
% Second part
for i=1:N
for j=1:(kmax)
    f=find(GRID==T_(i,j) | GRID==T_(i,j+1));
    s=f(1);
    e=f(2)-1;

 counter(X_(i,j), s:e)=counter(X_(i,j), s:e)+1;
end
end

该代码对随机过程执行 N 个不同的模拟(由 10^3 个事件组成,发生在取决于特定模拟的离散时刻(T 向量)。现在(第二部分)我想知道,作为时间常数的函数, 有多少模拟处于特定状态(X 假定值介于 1 和 10 之间)。我的想法是:创建一个网格向量,其中包含任何模拟中发生某些事情的所有时刻。然后,循环模拟,循环发生某些事情的时间步长,并递增与此特定时间片相对应的所有计数器 indeces。

然而,第二部分非常繁重(我的意思是在标准四核 CPU 上处理数天)。 它不应该。 是否有任何想法(也许是关于以更有效的方式比较向量)来减少 CPU 时间?

这是一个独立的“second_part”

N=5000;
counter=zeros(11,length(GRID));

for i=1:N
    disp(['Counting sim #' num2str(i)]);
    for j=1:(kmax)
        f=find(GRID==T_(i,j) | GRID==T_(i,j+1),2);
        s=f(1);
        e=f(2)-1;

        counter(X_(i,j), s:e)=counter(X_(i,j), s:e)+1;

    end
end

counter=counter/N;
stop=find(GRID==Tmin);
stop=stop-1;
plot(counter(:,(stop-500):stop)')

与相关的虚拟数据( filedropper.com/data_38 )。 在实际情况下,矩阵有 2x 行和 10x 列。

这是我的理解:

T_是来自 N 次模拟的时间步长矩阵。
X_是这些模拟中T_处的模拟状态矩阵。

所以如果你这样做:

[ut,~,ic]= unique(T_(:));

你得到ic ,它是T_所有唯一元素的索引向量。 然后你可以写:

counter = accumarray([ic X_(:)],1);

并得到没有的counter 行作为您唯一的时间步长,而不是。 列作为X_的唯一状态(它们都是并且必须是整数)。 现在你可以说对于每个时间步ut(k)模拟处于状态mcounter(k,m)

在您的数据中,值大于 1 的mk的唯一组合是(1,1)


编辑:

从下面的评论中,我了解到您记录了所有状态更改以及它们发生时的时间步长。 然后每次模拟更改状态时,您都希望从所有模拟中收集所有状态并计算每种类型有多少状态。

这里的主要问题是你的时间是连续的,所以基本上T_每个元素都是唯一的,并且你有超过一百万个时间步来循环。 完全矢量化这样的过程需要大约 80GB 的内存,这可能会卡住你的计算机。

所以我寻找向量化和循环时间步长的组合。 我们首先找到所有唯一的间隔,并预分配counter

ut = unique(T_(:));
stt = 11; % no. of states
counter = zeros(stt,numel(ut));r = 1:size(T_,1);
r = 1:size(T_,1); % we will need that also later

然后我们遍历ut所有元素,每次以矢量化方式在所有模拟中查找T_中的相关时间步长。 最后我们使用histcounts来计算所有状态:

for k = 1:numel(ut)
    temp = T_<=ut(k); % mark all time steps before ut(k)
    s = cumsum(temp,2); % count the columns
    col_ind = s(:,end); % fins the column index for each simulation
    % convert the coulmns to linear indices:
    linind = sub2ind(size(T_),r,col_ind.');
    % count the states:
    counter(:,k) = histcounts(X_(linind),1:stt+1);
end

在我的电脑上进行 1000 次模拟大约需要 4 秒,因此整个过程增加了一个多小时。 不是很快...

您还可以尝试以下一两个调整来缩短运行时间:

  1. 正如你可以在这里阅读accumarray似乎在小数组更快的工作,然后histcouns 所以可能想切换到它。

  2. 此外,直接计算线性索引比sub2ind更快,因此您可能想尝试一下。

在上面的循环中实施这些建议,我们得到:

R = size(T_,1);
r = (1:R).';
for k = 1:K
    temp = T_<=ut(k); % mark all time steps before ut(k)
    s = cumsum(temp,2); % count the columns
    col_ind = s(:,end); % fins the column index for each simulation
    % convert the coulmns to linear indices:
    linind = R*(col_ind-1)+r;
    % count the states:
    counter(:,k) = accumarray(X_(linind),1,[stt 1]);
end

在我的计算机中切换到accumarray和/或删除sub2ind获得了轻微的改进,但它并不一致(使用timeitut 100 或 1K 元素进行测试),所以你最好自己测试一下。 然而,这仍然很长。


您可能需要考虑的一件事是尝试离散化您的时间步长,这样您循环的独特元素就会少得多。 在您的数据中,大约 8% 的时间间隔小于 1。如果您可以假设这足够短以被视为一个时间步长,那么您可以舍入您的T_并仅获得 ~12.5K 唯一元素,这需要大约一分钟循环。 您可以对 0.1 个间隔(小于时间间隔的 1%)执行相同操作,并获得 122K 个元素进行循环,大约需要 8 小时...

当然,以上所有时间都是使用相同算法的粗略估计。 如果您确实选择舍入时间,则可能有更好的方法来解决此问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM