RingBuffer.hpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4. *
  5. * (c) ZeroTier, Inc.
  6. * https://www.zerotier.com/
  7. */
  8. #ifndef ZT_RINGBUFFER_H
  9. #define ZT_RINGBUFFER_H
  10. #include <algorithm>
  11. #include <cstdint>
  12. #include <math.h>
  13. #include <memory.h>
  14. #include <stdlib.h>
  15. #include <typeinfo>
  16. namespace ZeroTier {
  17. /**
  18. * A circular buffer
  19. *
  20. * For fast handling of continuously-evolving variables (such as path quality metrics).
  21. * Using this, we can maintain longer sliding historical windows for important path
  22. * metrics without the need for potentially expensive calls to memcpy/memmove.
  23. *
  24. * Some basic statistical functionality is implemented here in an attempt
  25. * to reduce the complexity of code needed to interact with this type of buffer.
  26. */
  27. template <class T, size_t S> class RingBuffer {
  28. private:
  29. T buf[S];
  30. size_t begin;
  31. size_t end;
  32. bool wrap;
  33. public:
  34. RingBuffer() : begin(0), end(0), wrap(false)
  35. {
  36. memset(buf, 0, sizeof(T) * S);
  37. }
  38. /**
  39. * @return A pointer to the underlying buffer
  40. */
  41. inline T* get_buf()
  42. {
  43. return buf + begin;
  44. }
  45. /**
  46. * Adjust buffer index pointer as if we copied data in
  47. * @param n Number of elements to copy in
  48. * @return Number of elements we copied in
  49. */
  50. inline size_t produce(size_t n)
  51. {
  52. n = std::min(n, getFree());
  53. if (n == 0) {
  54. return n;
  55. }
  56. const size_t first_chunk = std::min(n, S - end);
  57. end = (end + first_chunk) % S;
  58. if (first_chunk < n) {
  59. const size_t second_chunk = n - first_chunk;
  60. end = (end + second_chunk) % S;
  61. }
  62. if (begin == end) {
  63. wrap = true;
  64. }
  65. return n;
  66. }
  67. /**
  68. * Fast erase, O(1).
  69. * Merely reset the buffer pointer, doesn't erase contents
  70. */
  71. inline void reset()
  72. {
  73. consume(count());
  74. }
  75. /**
  76. * adjust buffer index pointer as if we copied data out
  77. * @param n Number of elements we copied from the buffer
  78. * @return Number of elements actually available from the buffer
  79. */
  80. inline size_t consume(size_t n)
  81. {
  82. n = std::min(n, count());
  83. if (n == 0) {
  84. return n;
  85. }
  86. if (wrap) {
  87. wrap = false;
  88. }
  89. const size_t first_chunk = std::min(n, S - begin);
  90. begin = (begin + first_chunk) % S;
  91. if (first_chunk < n) {
  92. const size_t second_chunk = n - first_chunk;
  93. begin = (begin + second_chunk) % S;
  94. }
  95. return n;
  96. }
  97. /**
  98. * @param data Buffer that is to be written to the ring
  99. * @param n Number of elements to write to the buffer
  100. */
  101. inline size_t write(const T* data, size_t n)
  102. {
  103. n = std::min(n, getFree());
  104. if (n == 0) {
  105. return n;
  106. }
  107. const size_t first_chunk = std::min(n, S - end);
  108. memcpy(buf + end, data, first_chunk * sizeof(T));
  109. end = (end + first_chunk) % S;
  110. if (first_chunk < n) {
  111. const size_t second_chunk = n - first_chunk;
  112. memcpy(buf + end, data + first_chunk, second_chunk * sizeof(T));
  113. end = (end + second_chunk) % S;
  114. }
  115. if (begin == end) {
  116. wrap = true;
  117. }
  118. return n;
  119. }
  120. /**
  121. * Place a single value on the buffer. If the buffer is full, consume a value first.
  122. *
  123. * @param value A single value to be placed in the buffer
  124. */
  125. inline void push(const T value)
  126. {
  127. if (count() == S) {
  128. consume(1);
  129. }
  130. const size_t first_chunk = std::min((size_t)1, S - end);
  131. *(buf + end) = value;
  132. end = (end + first_chunk) % S;
  133. if (begin == end) {
  134. wrap = true;
  135. }
  136. }
  137. /**
  138. * @return The most recently pushed element on the buffer
  139. */
  140. inline T get_most_recent()
  141. {
  142. return *(buf + end);
  143. }
  144. /**
  145. * @param dest Destination buffer
  146. * @param n Size (in terms of number of elements) of the destination buffer
  147. * @return Number of elements read from the buffer
  148. */
  149. inline size_t read(T* dest, size_t n)
  150. {
  151. n = std::min(n, count());
  152. if (n == 0) {
  153. return n;
  154. }
  155. if (wrap) {
  156. wrap = false;
  157. }
  158. const size_t first_chunk = std::min(n, S - begin);
  159. memcpy(dest, buf + begin, first_chunk * sizeof(T));
  160. begin = (begin + first_chunk) % S;
  161. if (first_chunk < n) {
  162. const size_t second_chunk = n - first_chunk;
  163. memcpy(dest + first_chunk, buf + begin, second_chunk * sizeof(T));
  164. begin = (begin + second_chunk) % S;
  165. }
  166. return n;
  167. }
  168. /**
  169. * Return how many elements are in the buffer, O(1).
  170. *
  171. * @return The number of elements in the buffer
  172. */
  173. inline size_t count()
  174. {
  175. if (end == begin) {
  176. return wrap ? S : 0;
  177. }
  178. else if (end > begin) {
  179. return end - begin;
  180. }
  181. else {
  182. return S + end - begin;
  183. }
  184. }
  185. /**
  186. * @return The number of slots that are unused in the buffer
  187. */
  188. inline size_t getFree()
  189. {
  190. return S - count();
  191. }
  192. /**
  193. * @return The arithmetic mean of the contents of the buffer
  194. */
  195. inline float mean()
  196. {
  197. size_t iterator = begin;
  198. float subtotal = 0;
  199. size_t curr_cnt = count();
  200. for (size_t i = 0; i < curr_cnt; i++) {
  201. iterator = (iterator + S - 1) % curr_cnt;
  202. subtotal += (float)*(buf + iterator);
  203. }
  204. return curr_cnt ? subtotal / (float)curr_cnt : 0;
  205. }
  206. /**
  207. * @return The arithmetic mean of the most recent 'n' elements of the buffer
  208. */
  209. inline float mean(size_t n)
  210. {
  211. n = n < S ? n : S;
  212. size_t iterator = begin;
  213. float subtotal = 0;
  214. size_t curr_cnt = count();
  215. for (size_t i = 0; i < n; i++) {
  216. iterator = (iterator + S - 1) % curr_cnt;
  217. subtotal += (float)*(buf + iterator);
  218. }
  219. return curr_cnt ? subtotal / (float)curr_cnt : 0;
  220. }
  221. /**
  222. * @return The sum of the contents of the buffer
  223. */
  224. inline float sum()
  225. {
  226. size_t iterator = begin;
  227. float total = 0;
  228. size_t curr_cnt = count();
  229. for (size_t i = 0; i < curr_cnt; i++) {
  230. iterator = (iterator + S - 1) % curr_cnt;
  231. total += (float)*(buf + iterator);
  232. }
  233. return total;
  234. }
  235. /**
  236. * @return The sample standard deviation of element values
  237. */
  238. inline float stddev()
  239. {
  240. return sqrt(variance());
  241. }
  242. /**
  243. * @return The variance of element values
  244. */
  245. inline float variance()
  246. {
  247. size_t iterator = begin;
  248. float cached_mean = mean();
  249. size_t curr_cnt = count();
  250. T sum_of_squared_deviations = 0;
  251. for (size_t i = 0; i < curr_cnt; i++) {
  252. iterator = (iterator + S - 1) % curr_cnt;
  253. float deviation = (buf[i] - cached_mean);
  254. sum_of_squared_deviations += (T)(deviation * deviation);
  255. }
  256. float variance = (float)sum_of_squared_deviations / (float)(S - 1);
  257. return variance;
  258. }
  259. /**
  260. * @return The number of elements of zero value
  261. */
  262. inline size_t zeroCount()
  263. {
  264. size_t iterator = begin;
  265. size_t zeros = 0;
  266. size_t curr_cnt = count();
  267. for (size_t i = 0; i < curr_cnt; i++) {
  268. iterator = (iterator + S - 1) % curr_cnt;
  269. if (*(buf + iterator) == 0) {
  270. zeros++;
  271. }
  272. }
  273. return zeros;
  274. }
  275. /**
  276. * @param value Value to match against in buffer
  277. * @return The number of values held in the ring buffer which match a given value
  278. */
  279. inline size_t countValue(T value)
  280. {
  281. size_t iterator = begin;
  282. size_t cnt = 0;
  283. size_t curr_cnt = count();
  284. for (size_t i = 0; i < curr_cnt; i++) {
  285. iterator = (iterator + S - 1) % curr_cnt;
  286. if (*(buf + iterator) == value) {
  287. cnt++;
  288. }
  289. }
  290. return cnt;
  291. }
  292. /**
  293. * Print the contents of the buffer
  294. */
  295. /*
  296. inline void dump()
  297. {
  298. size_t iterator = begin;
  299. for (size_t i=0; i<S; i++) {
  300. iterator = (iterator + S - 1) % S;
  301. if (typeid(T) == typeid(int)) {
  302. fprintf(stderr, "buf[%2zu]=%2d\n", iterator, (int)*(buf + iterator));
  303. }
  304. else {
  305. fprintf(stderr, "buf[%2zu]=%2f\n", iterator, (float)*(buf + iterator));
  306. }
  307. }
  308. }
  309. */
  310. };
  311. } // namespace ZeroTier
  312. #endif