MATLAB 中的循环缓冲,**无需**复制旧数据

4
这里有一些好的帖子(例如这个),介绍如何在MATLAB中制作循环缓冲区。但是从它们的内容来看,我不认为它们适合我的应用,因为我正在寻找的是在MATLAB中实现的循环缓冲解决方案,而不涉及任何旧数据的复制。
举个简单的例子,假设我每次处理50个样本,并且每次读入10个样本。我将首先运行5次迭代,填充我的缓冲区,在最后处理我的50个样本。所以我的缓冲区将会是:
[B1 B2 B3 B4 B5]

每个“B”都是由10个样本组成的块。

现在,我读取了接下来的10个样本,称它们为B6。我希望我的缓冲区现在看起来像:

[B2 B3 B4 B5 B6]

问题在于 - 我不想每次都复制旧数据 B2、B3、B4、B5,因为这样会花费很多时间(我的数据集非常大)。
我想知道是否有一种方法可以在不复制“旧”数据的情况下完成此操作。谢谢。
3个回答

5

一种快速实现循环缓冲区的方法是使用模数将数据循环回到前端。这将略微修改数据的顺序,但如果你只是用最新的数据替换最旧的数据,这可能会更快且等效。所以,不要担心数据顺序的变化。

[B2 B3 B4 B5 B6]

您获得

[B6 B2 B3 B4 B5]

通过使用像这样的代码:
bufferSize = 5;

data = nan(bufferSize,1)';

for ind = 1:bufferSize+2  

    data(mod(ind-1, bufferSize)+1) = ind

end

这适用于任意大小的数据。

如果您不熟悉取模运算,mod函数实际上返回除法操作的余数。所以,mod(3,5)返回3mod(6,5)返回1mod(7,5)返回2,一直到达mod(10,5)再次等于0。这使我们可以通过每次到达结尾时回到开头来“环绕”向量。代码中的+1-1是因为MATLAB将其向量索引从1开始而不是0,因此在进行mod之前必须减去1,然后再添加回来以获取正确的索引。结果是当您尝试将第6个元素写入向量时,它会将其写入向量中的第1个位置。


谢谢,但我不确定我完全理解你的代码。你能否详细地扩展/解释一下?谢谢。 - Spacey
@Learnaholic 当然,我在代码上添加了一些解释。这样有帮助吗? - Steve
难道这不是和我的方法有些相似,只是去掉了索引变量吗?我的意思是,就像你上面描述的那样,条目也会处于错误的顺序!? - tim
2
@bjoern 在进一步检查您下面的代码后,它们是相似的。mod 命令可能更快且更紧凑。此外,该方法明确使用数组而不是单元数组。这意味着您无需修改现有代码或调用 cell2mat 或等效操作——这将需要不必要的复制操作。 - Steve
是的,你说得对,这样更紧凑,但有些用户可能更喜欢可读性更强的版本。但这是问卷调查决定的 :-) 但是:你的代码是否也可以处理使用包含多个条目的缓冲区数据的循环缓冲区?甚至长度不同的数据?当单元数组可能更方便时,情况就会如此。但据我所见,我们没有从提问者那里获得任何信息! - tim
@Steve 感谢您的澄清。您的帖子非常好 - 但是...我需要数据按照OP中显示的正确顺序排列,例如[B1 B2 B3 B4 B5],然后是[B2 B3 B4 B5 B6],然后是[B3 B4 B5 B6 B7]等等。我们如何在不复制旧数据的情况下实现正确的排序?(我感谢您的答案,并将保存它,因为如果顺序无关紧要,它可能对我有用)。谢谢。 - Spacey

1
我的想法是使用一个包含5个元素的cell数组,并使用变量来索引下一个步骤中应该被覆盖的子数组。例如,像这样:
 a = {ones(10),2*ones(10),3*ones(10),4*ones(10),5*ones(10)};
 index = 1;

在下一步中,您可以编写到子数组中:
 a{index} = 6*ones(10);

并增加索引,例如:
index = index+1

显然,有某种限制:
if(index > 5) % FIXED TYPO!!
   index = 1;
end

这是给你的吗?

编辑: 另外需要注意的一点是条目的排序,因此始终会有一些条目被移动,但根据您如何使用数据,您可以根据变量index来移动数据的使用。

编辑2: 我有另一个想法:在MATLAB中使用类。您可以使用句柄类来保存数据,从而仅使用缓冲区引用数据。这取决于您保存的数据(数据集有多大等)以及代码中需要进行多少次移位,这可能会使其速度更快。例如,请参见此处:Matlab -- handle objects

您可以使用简单的句柄类:

classdef Foo < handle   
    properties (SetAccess = public, GetAccess = public)
        x
    end

    methods
        function obj = foo(x)
            % constructor
            obj.x = x;
        end 
    end       
end

将数据存储在其中:
data = [1 2 3 4];
foo = Foo(data);  % handle object

然后只需将对象引用存储在循环缓冲区中。在发布的链接中,答案表明赋值bar = foo不会复制对象,而是仅保留引用:

foo.x = [3 4]
disp(bar.x)      % would be [3 4]

但是如上所述,我不知道由于面向对象的开销是否会更快。这可能取决于您的数据...以下是有关此的更多信息:http://www.matlabtips.com/how-to-point-at-in-matlab/


你说的“forever”是什么意思? - tim
bjoern,感谢您的代码。不幸的是,我仍然不知道如何应用它。最终,我需要一个向量v = [B2 B3 B4 B5 B6]。然后下一次迭代,我需要v = [B3 B4 B5 B5 B7],以此类推,_按照这个顺序_。这如何帮助实现这一目标?谢谢。 - Spacey
这与Steve的答案相同,它不复制数据,但是不保留顺序是不可能的,除非进行复制。正如我上面所述,我认为如果您无论如何都要使用另一个函数调用您的数据,那么这可能仍然对您有所帮助,您可以简单地移动被调用函数的输入参数。但是,如果情况不是这样,您只需要将缓冲区作为一个有序值列表,我感觉除了重新排序(因此复制数据)没有其他选择:(同样适用于其他答案。不幸的是,对于我来说:无法为与下面相同的方法点赞:( - tim
@bjoern我点赞了你。顺便问一下,那真的是你的照片吗? - Spacey
很好;-)所以,实际上答案是否定的;-) - tim
显示剩余5条评论

0

我刚刚上传了一个快速循环缓冲区的解决方案,它不会复制旧数据。

http://www.mathworks.com/matlabcentral/fileexchange/47025-circvbuf-m

这个循环缓冲区的主要思想是在程序中使用缓冲区时保持恒定和快速的性能,并避免复制操作。
% create a circular vector buffer
    bufferSz = 1000;
    vectorLen= 7;
    cvbuf = circVBuf(int64(bufferSz),int64(vectorLen));

% fill buffer with 99 vectors
    vecs = zeros(99,vectorLen,'double');
    cvbuf.append(vecs);

% loop over lastly appended vectors of the circVBuf:
    new = cvbuf.new;
    lst = cvbuf.lst;
    for ix=new:lst
       vec(:) = cvbuf.raw(:,ix);
    end

% or direct array operation on lastly appended vectors in the buffer (no copy => fast)
    new = cvbuf.new;
    lst = cvbuf.lst;
    mean = mean(cvbuf.raw(3:7,new:lst));

查看屏幕截图,可以看出如果缓冲区很大但每次附加的数据大小很小,则circVBuf的性能不依赖于缓冲区大小,相比简单的复制缓冲区具有优势。

双重缓冲保证了附加操作在任何情况下都具有预测时间。将来,这个类将为您提供选择是否使用双重缓冲 - 如果您不需要保证的时间,则会加速。

enter image description here

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接