mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
circularBuffer.hpp
Go to the documentation of this file.
1 /** \file circularBuffer.hpp
2  * \author Jared R. Males
3  * \brief A circular buffer class
4  * \ingroup signal_processing_files
5  *
6  */
7 
8 //***********************************************************************//
9 // Copyright 2020 Jared R. Males (jaredmales@gmail.com)
10 //
11 // This file is part of mxlib.
12 //
13 // mxlib is free software: you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation, either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // mxlib is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with mxlib. If not, see <http://www.gnu.org/licenses/>.
25 //***********************************************************************//
26 
27 #ifndef sigproc_circularBuffer
28 #define sigproc_circularBuffer
29 
30 #include <vector>
31 
32 namespace mx
33 {
34 namespace sigproc
35 {
36 
37 /** \defgroup circular_buffer Circular Buffer
38  * \brief A circular buffer class
39  *
40  * Three options for the circular buffer are provided, each inheriting the same underlying interface. The choices
41  * vary the way in which the wrapping is handled at the end of the storage. These are:
42  * - circularBufferMod: uses the mod operator, this is slowest in all situations and is provided for comparison
43  * - circularBufferBranch: uses an if statement (branch) to test for location in memory. Slower in sequential access, possibly slightly faster in random access.
44  * - circularBufferIndex: uses a pre-populated index array, which adds some memory overhead. Fastest in sequential access, possibly slightly slower in random access.
45  *
46  * Benchmarks are clear that circularBufferIndex is fastest for sequential acceess, e.g. one element after the other in sequential order, by something like 30%.
47  * For random access circularBufferBranch is possibly slightly faster, but not enough tests were performed to be conclusive.
48  *
49  * circularBufferMod is always much slower due to use of the `%` operator.
50  *
51  * The memory overhead of circularBufferIndex is `2*maxEntries*sizeof(indexT)`, where `maxEntries` is the maximum length of the buffer, and indexT is the
52  * type used for indexing and sizes.
53  *
54  * \todo perform circular buffer testing on an isolated core, with one test per executable
55  * \todo implement a circular buffer with fixed power-of-two size to test `&` modulo
56  *
57  * \ingroup signal_processing
58  */
59 
60 /// CRTP base class for all circular buffers, providing the underlying memory management and accessors.
61 /** The CRTP derived classes implement a standard interface based on how they handle wrapping from the end
62  * to the beginning of the buffer.
63  *
64  * \tparam _derivedT the child class type
65  * \tparam _storedT the type stored in teh circular buffer
66  * \tparam _indexT the index type, also used for sizes (can be unsigned)
67  *
68  * \ingroup circular_buffer
69  */
70 template<typename _derivedT, typename _storedT, typename _indexT>
72 {
73 public:
74 
75  typedef _derivedT derivedT; ///< The child class
76 
77  typedef _storedT storedT; ///< The type stored in the circular buffer
78  typedef _indexT indexT; ///< The index type, also used for sizes
79 
80 protected:
81 
82  std::vector<storedT> m_buffer; ///< The circular buffer storage
83 
84  indexT m_maxEntries {0}; ///< The maximum number of entries to allow in the buffer before wrapping
85  indexT m_nextEntry {0}; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer.
86  indexT m_latest {0}; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer.
87 
88  uint64_t m_mono{0}; ///< A monotonic counter, which is incremented for each new entry, and reset on call to maxEntries.
89 
90 public:
91 
92  /// Default c'tor
94 
95  /// Sizing constructor
96  /** Sets the maximum size of the buffer. Note that this will not be the size until
97  * a full set of entries have been added to the buffer.
98  */
99  explicit circularBufferBase(indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/);
100 
101  /// Set the maximum size of the buffer.
102  /** Note that this will not be the size until
103  * a full set of entries have been added to the buffer.
104  */
105  void maxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/);
106 
107  /// Get the maximum size of the buffer.
108  /** Note that this is not the current size of the buffer until
109  * at least maxEntries have been added. Use size() to check current size.
110  *
111  * \returns the maximum size of the buffer, m_maxEntries
112  */
114 
115  /// Get the number of entries.
116  /** Note that this is the current size. It will not
117  * be the same as maxEntries() until at least maxEntries
118  * have been added.
119  */
120  indexT size() const;
121 
122  /// Add the next entry to the circular buffer
123  /** Adds the entry (will incur a deep copy) and updates
124  * the wrapping system.
125  */
126  void nextEntry( const storedT & newEnt /**< [in] the new entry to add to the buffer*/);
127 
128  /// Returns the index of the earliest entry
129  /** This is particularly useful for accessing entries with the at() function
130  * over an unchanging sequence if asynchronous updates are possible.
131  */
133 
134  /// Returns the index of the latest entry
135  /**
136  */
138 
139  uint64_t mono();
140 
141  /// Get the entry at a given index
142  /** `idx=0` is the earliest entry in the buffer. `idx=1` is the one after that.
143  * I.e., this counts forward from the oldest data
144  *
145  * \returns a reference to the indicated entry in the circular buffer.
146  */
147  storedT & operator[](indexT idx /**< [in] the index of the entry to access*/);
148 
149  const storedT & operator[](indexT idx /**< [in] the index of the entry to access*/) const;
150 
151  /// Get the entry at a given index relative a fixed reference entry
152  /** `idx=0` is the entry at refEntry. `idx=1` is the one after that.
153  * I.e., this counts forward from the oldest data
154  *
155  * \returns a reference to the indicated entry in the circular buffer.
156  */
157  storedT & at( indexT refEntry, ///< [in] the entry to start counting from
158  indexT idx ///< [in] the index of the entry to access
159  );
160 
161  /// Get the entry at a given index relative a fixed start entry, const version
162  /** `idx=0` is the entry at refEntry. `idx=1` is the one after that.
163  * I.e., this counts forward from the oldest data
164  *
165  * \overload
166  *
167  * \returns a const reference to the indicated entry in the circular buffer.
168  */
169  const storedT & at( indexT refEntry, ///< [in] the entry to start counting from
170  indexT idx ///< [in] the index of the entry to access
171  ) const;
172 
173 private:
174  derivedT & derived()
175  {
176  return *static_cast<derivedT *>(this);
177  }
178 
179  const derivedT & derived() const
180  {
181  return *static_cast<const derivedT *>(this);
182  }
183 
184 };
185 
186 template<class derivedT, typename storedT, typename indexT>
188 {
189 }
190 
191 template<class derivedT, typename storedT, typename indexT>
193 {
194  maxEntries(maxEnt);
195 }
196 
197 template<class derivedT, typename storedT, typename indexT>
199 {
200  m_buffer.clear();
201  m_nextEntry = 0;
202  m_latest = 0;
203 
204  m_maxEntries = maxEnt;
205  m_buffer.reserve(m_maxEntries);
206 
207  derived().setMaxEntries(maxEnt);
208 
209  m_mono = 0;
210 
211 }
212 
213 template<class derivedT, typename storedT, typename indexT>
215 {
216  return m_maxEntries;
217 }
218 
219 template<class derivedT, typename storedT, typename indexT>
221 {
222  return m_buffer.size();
223 }
224 
225 template<class derivedT, typename storedT, typename indexT>
227 {
228  if( m_buffer.size() < m_maxEntries )
229  {
230  m_buffer.push_back(newEnt);
231  m_latest = m_buffer.size()-1;
232  m_nextEntry = 0;
233  derived().setWrapStartup();
234  }
235  else
236  {
237  m_buffer[m_nextEntry] = newEnt;
238  m_latest = m_nextEntry;
239  ++m_nextEntry;
240  if(m_nextEntry >= m_buffer.size()) m_nextEntry = 0;
241  derived().setWrap();
242  }
243 
244  ++m_mono;
245 
246 }
247 
248 template<class derivedT, typename storedT, typename indexT>
250 {
251  return m_nextEntry;
252 }
253 
254 template<class derivedT, typename storedT, typename indexT>
256 {
257  return m_latest;
258 }
259 
260 template<class derivedT, typename storedT, typename indexT>
262 {
263  return m_mono;
264 }
265 
266 template<class derivedT, typename storedT, typename indexT>
268 {
269  return derived().at(m_nextEntry, idx);
270 }
271 
272 template<class derivedT, typename storedT, typename indexT>
274 {
275  return derived().at(m_nextEntry, idx);
276 }
277 
278 template<class derivedT, typename storedT, typename indexT>
280  indexT idx
281  )
282 {
283  return derived().at(refEntry, idx);
284 }
285 
286 template<class derivedT, typename storedT, typename indexT>
288  indexT idx
289  ) const
290 {
291  return derived().at(refEntry, idx);
292 }
293 
294 /// Circular buffer which wraps with an if statement (branching) [faster than mod, less memory than index]
295 /**
296  * \ingroup circular_buffer
297  */
298 template<typename _storedT, typename _indexT>
299 class circularBufferBranch : public circularBufferBase<circularBufferBranch<_storedT,_indexT>, _storedT, _indexT>
300 {
301 public:
302 
303  typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
304  typedef _indexT indexT; ///< The index type, also used for sizes
305 
306 protected:
307  indexT m_wrapEntry{0};
308 
309 public:
310 
311  /// Default c'tor
313  {
314  }
315 
316  /// Sizing constructor
317  /** Sets the maximum size of the buffer. Note that this will not be the size until
318  * a full set of entries have been added to the buffer.
319  */
320  explicit circularBufferBranch(indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/) : circularBufferBase<circularBufferBranch,storedT,indexT>(maxEnt)
321  {
322  }
323 
324  /// Interface implementation for maxEntries.
325  /** Resets the wrap entry to 0.
326  */
327  void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/)
328  {
329  m_wrapEntry = 0;
330  }
331 
332  /// Interface implementation for wrapping setup during the startup phase
333  /** This is called before maxEntries have been added.
334  */
336  {
337  m_wrapEntry = this->m_buffer.size();
338  }
339 
340  /// Interface implementation for wrapping setup after the startup phase
341  /** This is called after maxEntries have been added.
342  */
343  void setWrap()
344  {
345  m_wrapEntry = this->m_maxEntries - this->m_nextEntry - 1;
346  }
347 
348  /// Interface implementation for entry access
349  /** Accesses the idx-th element relative to refEntry, using a branch (if-statement) to wrap
350  *
351  * \returns a reference to the idx-th element
352  */
353  storedT & at( indexT refEntry, ///< [in] the entry to start counting from
354  indexT idx ///< [in] the index of the entry to access
355  )
356  {
357  if(idx > this->m_wrapEntry) return this->m_buffer[refEntry + idx - this->m_buffer.size()];
358  else return this->m_buffer[refEntry + idx];
359  }
360 
361  /// Interface implementation for entry access, const version
362  /** Accesses the idx-th element relative to refEntry, using a branch (if-statement) to wrap
363  *
364  * \overload
365  *
366  * \returns a const reference to the idx-th element
367  */
368  const storedT & at( indexT refEntry, ///< [in] the entry to start counting from
369  indexT idx ///< [in] the index of the entry to access
370  ) const
371  {
372  if(idx > this->m_wrapEntry) return this->m_buffer[refEntry + idx - this->m_buffer.size()];
373  else return this->m_buffer[refEntry + idx];
374  }
375 
376 };
377 
378 /// Circular buffer which wraps with the mod opoerator [very slow]
379 /**
380  * \ingroup circular_buffer
381  */
382 template<typename _storedT, typename _indexT>
383 class circularBufferMod : public circularBufferBase<circularBufferMod<_storedT,_indexT>, _storedT, _indexT>
384 {
385 public:
386  typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
387  typedef _indexT indexT; ///< The index type, also used for sizes
388 
389  /// Default c'tor
391  {
392  }
393 
394  /// Sizing constructor
395  /** Sets the maximum size of the buffer. Note that this will not be the size until
396  * a full set of entries have been added to the buffer.
397  */
398  explicit circularBufferMod(indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/) : circularBufferBase<circularBufferMod,storedT,indexT>(maxEnt)
399  {
400  }
401 
402  /// Interface implementation for maxEntries.
403  /** A no-op
404  */
405  void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/)
406  {
407  }
408 
409  /// Interface implementation for wrapping setup during the startup phase
410  /** This is called before maxEntries have been added.
411  */
413  {
414  }
415 
416  /// Interface implementation for wrapping setup after the startup phase
417  /** This is called after maxEntries have been added.
418  */
419  void setWrap()
420  {
421  }
422 
423  /// Interface implementation for entry access
424  /** Accesses the idx-th element relative to refEntry, using the mod operator to wrap
425  *
426  * \returns a reference to the idx-th element
427  */
428  storedT & at( indexT refEntry, ///< [in] the entry to start counting from
429  indexT idx ///< [in] the index of the entry to access
430  )
431  {
432  return this->m_buffer[(refEntry + idx) % this->m_buffer.size()];
433  }
434 
435  /// Interface implementation for entry access, const version
436  /** Accesses the idx-th element relative to refEntry, using the mod operator to wrap
437  *
438  * \overload
439  *
440  * \returns a const reference to the idx-th element
441  */
442  const storedT & at( indexT refEntry, ///< [in] the entry to start counting from
443  indexT idx ///< [in] the index of the entry to access
444  ) const
445  {
446  return this->m_buffer[(refEntry + idx) % this->m_buffer.size()];
447  }
448 
449 };
450 
451 /// Circular buffer which wraps with a pre-populated indices array [generally fastest]
452 /**
453  * \ingroup circular_buffer
454  */
455 template<typename _storedT, typename _indexT>
456 class circularBufferIndex : public circularBufferBase<circularBufferIndex<_storedT,_indexT>, _storedT, _indexT>
457 {
458 public:
459  typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
460  typedef _indexT indexT; ///< The index type, also used for sizes
461 
462 protected:
463 
464  std::vector<size_t> m_indices; ///< Vector of indices for fast indexing into parent's m_buffer
465 
466 public:
467 
468  /// Default c'tor
470  {
471  }
472 
473  /// Sizing constructor
474  /** Sets the maximum size of the buffer. Note that this will not be the size until
475  * a full set of entries have been added to the buffer.
476  */
477  explicit circularBufferIndex(indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/) : circularBufferBase<circularBufferIndex,storedT,indexT>(maxEnt)
478  {
479  }
480 
481  /// Interface implementation for maxEntries.
482  /** Resizes and populates the indices array.
483  */
484  void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/)
485  {
486  m_indices.resize(2*this->m_maxEntries);
487  for(size_t i=0; i< this->m_maxEntries; ++i)
488  {
489  m_indices[i] = i;
490  m_indices[this->m_maxEntries + i] = i;
491  }
492  }
493 
494  /// Interface implementation for wrapping setup during the startup phase
495  /** This is called before maxEntries have been added.
496  */
498  {
499  }
500 
501  /// Interface implementation for wrapping setup after the startup phase
502  /** This is called after maxEntries have been added.
503  */
504  void setWrap()
505  {
506  }
507 
508  /// Interface implementation for entry access
509  /** Accesses the idx-th element relative to refEntry, using the pre-populated indices to wrap
510  *
511  * \returns a reference to the idx-th element
512  */
513  storedT & at( indexT refEntry, ///< [in] the entry to start counting from
514  indexT idx ///< [in] the index of the entry to access
515  )
516  {
517  return this->m_buffer[m_indices[refEntry + idx]];
518  }
519 
520  /// Interface implementation for entry access, const version
521  /** Accesses the idx-th element relative to refEntry, using the pre-populated indices to wrap
522  *
523  * \overload
524  *
525  * \returns a const reference to the idx-th element
526  */
527  const storedT & at( indexT refEntry, ///< [in] the entry to start counting from
528  indexT idx ///< [in] the index of the entry to access
529  ) const
530  {
531  return this->m_buffer[m_indices[refEntry + idx]];
532  }
533 
534 };
535 
536 } //namespace sigproc
537 } //namespace circularBuffer
538 
539 #endif //sigproc_circularBuffer
CRTP base class for all circular buffers, providing the underlying memory management and accessors.
storedT & at(indexT refEntry, indexT idx)
Get the entry at a given index relative a fixed reference entry.
storedT & operator[](indexT idx)
Get the entry at a given index.
circularBufferBase(indexT maxEnt)
Sizing constructor.
indexT maxEntries()
Get the maximum size of the buffer.
std::vector< storedT > m_buffer
The circular buffer storage.
void maxEntries(indexT maxEnt)
Set the maximum size of the buffer.
indexT size() const
Get the number of entries.
const storedT & at(indexT refEntry, indexT idx) const
Get the entry at a given index relative a fixed start entry, const version.
indexT earliest()
Returns the index of the earliest entry.
indexT latest()
Returns the index of the latest entry.
const storedT & operator[](indexT idx) const
indexT m_maxEntries
The maximum number of entries to allow in the buffer before wrapping.
_derivedT derivedT
The child class.
uint64_t m_mono
A monotonic counter, which is incremented for each new entry, and reset on call to maxEntries.
_storedT storedT
The type stored in the circular buffer.
_indexT indexT
The index type, also used for sizes.
indexT m_latest
Index into m_buff of the latest entry. This is the newest entry in the buffer.
indexT m_nextEntry
Index into m_buffer of the next entry. This is the oldest entry in the buffer.
void nextEntry(const storedT &newEnt)
Add the next entry to the circular buffer.
Circular buffer which wraps with an if statement (branching) [faster than mod, less memory than index...
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
void setMaxEntries(indexT maxEnt)
Interface implementation for maxEntries.
void setWrapStartup()
Interface implementation for wrapping setup during the startup phase.
storedT & at(indexT refEntry, indexT idx)
Interface implementation for entry access.
void setWrap()
Interface implementation for wrapping setup after the startup phase.
const storedT & at(indexT refEntry, indexT idx) const
Interface implementation for entry access, const version.
circularBufferBranch(indexT maxEnt)
Sizing constructor.
_indexT indexT
The index type, also used for sizes.
Circular buffer which wraps with a pre-populated indices array [generally fastest].
std::vector< size_t > m_indices
Vector of indices for fast indexing into parent's m_buffer.
const storedT & at(indexT refEntry, indexT idx) const
Interface implementation for entry access, const version.
_indexT indexT
The index type, also used for sizes.
circularBufferIndex(indexT maxEnt)
Sizing constructor.
void setWrap()
Interface implementation for wrapping setup after the startup phase.
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
storedT & at(indexT refEntry, indexT idx)
Interface implementation for entry access.
void setWrapStartup()
Interface implementation for wrapping setup during the startup phase.
void setMaxEntries(indexT maxEnt)
Interface implementation for maxEntries.
Circular buffer which wraps with the mod opoerator [very slow].
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
void setWrapStartup()
Interface implementation for wrapping setup during the startup phase.
_indexT indexT
The index type, also used for sizes.
const storedT & at(indexT refEntry, indexT idx) const
Interface implementation for entry access, const version.
storedT & at(indexT refEntry, indexT idx)
Interface implementation for entry access.
circularBufferMod(indexT maxEnt)
Sizing constructor.
void setWrap()
Interface implementation for wrapping setup after the startup phase.
void setMaxEntries(indexT maxEnt)
Interface implementation for maxEntries.
The mxlib c++ namespace.
Definition: mxError.hpp:107