4

There are some good posts on here (such as this one) on how to make a circular buffer in MATLAB. However from looking at them, I do not believe they fit my application, because what I am seeking, is a circular buffer solution in MATLAB, that does NOT involve any copying of old data.

To use a simple example, let us say that I am processing 50 samples at a time, and I read in 10 samples each iteration. I would first run through 5 iterations, fill up my buffer, and in the end, process my 50 samples. So my buffer will be

[B1 B2 B3 B4 B5]

, where each 'B' is a block of 10 samples.

Now, I read in the next 10 samples, call them B6. I want my buffer to now look like:

[B2 B3 B4 B5 B6]

The catch is this - I do NOT want to copy the old data, B2, B3, B4, B5 everytime, because it becomes expensive in time. (I have very large data sets).

I am wondering if there is a way to do this without any copying of 'old' data. Thank you.

Community
  • 1
  • 1
Spacey
  • 2,941
  • 10
  • 47
  • 63

3 Answers3

5

One way to quickly implement a circular buffer is to use modulus to circle back around to the front. This will slightly modify the order of the data from what you specified but may be faster and equivalent if you simply replace the oldest data with the newest so instead of

[B2 B3 B4 B5 B6]

You get

[B6 B2 B3 B4 B5]

By using code like this:

bufferSize = 5;

data = nan(bufferSize,1)';

for ind = 1:bufferSize+2  

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

end

and this works for arbitrary sized data.

If you're not familiar with modulo, the mod function returns effectively the remainder of a division operation. So mod(3,5) returns3, mod(6,5) returns 1, mod(7,5) returns 2 and so on until you reach mod(10,5) which equals 0 again. This allows us to 'wrap around' the vector by moving back to the start every time we reach the end. The +1 and -1 in the code is because MATLAB starts its vector indices at 1 rather than 0 so to get the math to work out right you have to remove 1 before doing the mod then add it back in to get the right index. The result is that when you try and write the 6th element to your vector is goes and writes it to the 1st position in the vector.

Steve
  • 3,957
  • 2
  • 26
  • 50
  • Thank you, but I am not sure I follow your code at all. May you please expand/explain it in more detail? Thanks. – Spacey Jan 02 '14 at 19:40
  • @Learnaholic Sure, I added a bit of explanation on the code. Does that help? – Steve Jan 02 '14 at 19:54
  • Isn't it somehow similar to my approach just leaving away the index variable? I mean as you described above the entries wil also be of the wrong order!? – tim Jan 02 '14 at 20:01
  • 2
    @bjoern After examining your code below further, they are similar. The `mod` command may be faster and is more compact, though. Also, this method explicitly uses an array rather than a cell array. This means that you don't have to modify the existing code or call `cell2mat` or equivalent---which would require the undesirable copy operation. – Steve Jan 02 '14 at 20:16
  • Yeah you are right, it's more compact, but some users *may* prefer a more readable version. But that's the questionairs decision :-) But: Could your code also handle a circular buffer using buffer data containing more than one entry? Or even data with different lengths? That would be the case when the cell-array might be handier. But as far as I can see we've got no information about that from the asker! – tim Jan 02 '14 at 21:41
  • @Steve Thanks for the clarification. Your post is very nice - but... I need the data to be in the proper order as shown in the OP, For example, [B1 B2 B3 B4 B5], then [B2 B3 B4 B5 B6], then [B3 B4 B5 B6 B7], etc. How can we attain proper ordering without copying old data? (I appreciate your answer and I will save it since it might be useful for me if the order does not matter). Thanks. – Spacey Jan 03 '14 at 12:39
1

My idea would be to use a cell-array with 5 entries and use a variable to index the sub-array which should be overwritten in the next step. E.g. something like

 a = {ones(10),2*ones(10),3*ones(10),4*ones(10),5*ones(10)};
 index = 1;

in the next step you could then write into the sub-array:

 a{index} = 6*ones(10);

and increase the index like

index = index+1

Obviously, some sort of limitation:

if(index > 5) % FIXED TYPO!!
   index = 1;
end

Would that be for you?

EDIT: One more thing to look at would be the sortation of the entries which would therefore always be shifted by some entries but depending on how you keep on using the data, you could e.g. shift the usage of the data depending on the variable index.

EDIT2: I've got another idea: What about using classes in MATLAB. You could use a handle-class to hold your data thus using the buffer to only reference to the data. This might make it a bit faster, depending on what data (how large the datasets are etc) you hold and how many shifts you'll have in your code. See e.g. here: Matlab -- handle objects

You could use a simple handle class:

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

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

Store the data in it:

data = [1 2 3 4];
foo = Foo(data);  % handle object

and then only store the object-reference in the circular buffer. In the posted link the answer shows that the assignment bar = foo doesn't copy the object but really only helds the reference:

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

but as stated, I don't know if that will be faster because of the OOP-overhead. It might be depending on your data... And here some more information about it: http://www.matlabtips.com/how-to-point-at-in-matlab/

Community
  • 1
  • 1
tim
  • 9,896
  • 20
  • 81
  • 137
  • Umm, this would make "a" grow in size forever, right? I not sure how this solves the problem...how would you even access a{2} to a{6} as a continuous vector? – Spacey Jan 02 '14 at 19:32
  • What do you mean by forever? – tim Jan 02 '14 at 19:41
  • "a" will keep growing that way I understand you have it here. – Spacey Jan 02 '14 at 19:43
  • Why grow? You simply overwrite existing entries by using the index refering to a sub-array of `a`, like in the solution posted by Steve which also uses a pre-allocated variable `data` which, in the for-loop, gets overwritten partially in each iteration. – tim Jan 02 '14 at 20:07
  • @bjoern This code won't work as `if(index < 5)`. It must be `if(index > 5)` – Steve Jan 02 '14 at 20:18
  • bjoern, Thanks for your code. Unfortunately I still do not know how this applies. At the end of the day, I need a vector, v = [B2 B3 B4 B5 B6]. Then next iteration, I need v = [B3 B4 B5 B5 B7], etc, _in that order_. How does this help accomplish this? Thanks. – Spacey Jan 03 '14 at 13:27
  • It's the same as Steve's answer, it doesn't copy data but does NOT keep the ordner which isn't possible without copying I feel. As I stated above, I thought maybe this could still be helpful for you in case you are calling another function with your data anyhow, you could simply shift the input arguments of the called function. But if that's not the case and you simply need the buffer as a ordered list of values, I feel there's nothing you could do instead of reordering (thus copying the data) :( Same applies to the other answer. Unfortunately for me: no upvote for the same approach as below:( – tim Jan 03 '14 at 13:55
  • @bjoern Yeah, I guess there is no real way without copying the data and maintaining the order. > – Spacey Jan 03 '14 at 14:22
  • @bjoern I upvoted you. BTW is that really you in the picture?? – Spacey Jan 03 '14 at 15:34
  • Would be nice ;-) So, the answer is actually no ;-) – tim Jan 03 '14 at 17:08
0

I just uploaded my solution for a fast circular buffer to which does not copy old data

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

The main idea of this circular buffer is constant and fast performance and avoiding copy operations when using the buffer in a program:

% 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));

Check the screenshot to see, that this circular buffer has advantages if the buffer is large, but the size of data to append each time is small as the performance of circVBuf does NOT depend on the buffer size, compared to a simple copy buffer.

The double buffering garanties a predictive time for an append depending on the data to append in any situation. In future this class shall give you a choice for double buffering yes or no - things will speedup, if you do not need the garantied time. enter image description here