From dacf81745ebbccadd6e77fdf97ec1c0bb216ff1b Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Tue, 30 Sep 2014 19:06:54 +0300 Subject: [PATCH] core: add circular_buffer Since we have lots of queues, we need an efficient queue structure, esp. for moveable types. libstdc++'s std::deque is quite hairy, and boost's circular_buffer_space_optimized uses assignments instead of constructors, which are both slower and less available than constructors. This patch implements a growable circular buffer for these needs. --- core/circular_buffer.hh | 301 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 core/circular_buffer.hh diff --git a/core/circular_buffer.hh b/core/circular_buffer.hh new file mode 100644 index 0000000000..e209be75cd --- /dev/null +++ b/core/circular_buffer.hh @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2014 Cloudius Systems, Ltd. + */ + +#ifndef CIRCULAR_BUFFER_HH_ +#define CIRCULAR_BUFFER_HH_ + +// A growable double-ended queue container that can be efficiently +// extended (and shrunk) from both ends. Implementation is a single +// storage vector. +// +// Similar to libstdc++'s std::deque, except that it uses a single level +// store, and so is more efficient for simple stored items. +// Similar to boost::circular_buffer_space_optimized, except it uses +// uninitialized storage for unoccupied elements (and thus move/copy +// constructors instead of move/copy assignments, which are less efficient). + +#include "transfer.hh" +#include +#include + +template > +class circular_buffer { + struct impl : Alloc { + T* storage = nullptr; + T* begin = nullptr; + T* end = nullptr; // never points at storage+capacity + size_t size = 0; + size_t capacity = 0; + }; + impl _impl; +public: + using value_type = T; + using size_type = size_t; + using reference = T&; + using pointer = T*; + using const_reference = const T&; + using const_pointer = const T*; +public: + circular_buffer() = default; + circular_buffer(circular_buffer&& X); + circular_buffer(const circular_buffer& X) = delete; + ~circular_buffer(); + circular_buffer& operator=(const circular_buffer&) = delete; + circular_buffer& operator=(circular_buffer&&) = delete; + void push_front(const T& data); + void push_front(T&& data); + template + void emplace_front(A... args); + void push_back(const T& data); + void push_back(T&& data); + template + void emplace_back(A... args); + T& front(); + T& back(); + void pop_front(); + void pop_back(); + bool empty() const; + size_t size() const; + size_t capacity() const; + template + void for_each(Func func); +private: + void expand(); + void maybe_expand(size_t nr = 1); + T* pre_push_front(); + T* pre_push_back(); + void post_push_front(T* p); + void post_push_back(T* p); +}; + +template +inline +bool +circular_buffer::empty() const { + return _impl.begin == _impl.end; +} + +template +inline +size_t +circular_buffer::size() const { + return _impl.size; +} + +template +inline +size_t +circular_buffer::capacity() const { + // we never use all of the elements, since end == begin means empty + return _impl.capacity ? _impl.capacity - 1 : 0; +} + +template +inline +circular_buffer::circular_buffer(circular_buffer&& x) + : _impl(std::move(x._impl)) { + x._impl = {}; +} + +template +template +inline +void +circular_buffer::for_each(Func func) { + auto p = _impl.begin; + auto e = _impl.storage + _impl.capacity; + if (p > _impl.end) { + while (p < e) { + func(*p++); + } + p = _impl.storage; + } + while (p < _impl.end) { + func(*p++); + } +} + +template +inline +circular_buffer::~circular_buffer() { + for_each([this] (T& obj) { + _impl.destroy(&obj); + }); + _impl.deallocate(_impl.storage, _impl.capacity); +} + +template +void +circular_buffer::expand() { + auto new_cap = std::max(_impl.capacity * 2, 2); + auto new_storage = _impl.allocate(new_cap); + auto p = new_storage; + try { + for_each([this, &p] (T& obj) { + transfer_pass1(_impl, &obj, p++); + }); + } catch (...) { + while (p != new_storage) { + _impl.destroy(--p); + } + _impl.deallocate(new_storage, new_cap); + throw; + } + p = new_storage; + for_each([this, &p] (T& obj) { + transfer_pass2(_impl, &obj, p++); + }); + std::swap(_impl.storage, new_storage); + std::swap(_impl.capacity, new_cap); + _impl.begin = _impl.storage; + _impl.end = p; + _impl.deallocate(new_storage, new_cap); +} + +template +inline +void +circular_buffer::maybe_expand(size_t nr) { + // one item is always unused + if (_impl.size + nr >= _impl.capacity) { + expand(); + } +} + +template +inline +T* +circular_buffer::pre_push_front() { + maybe_expand(); + if (_impl.begin == _impl.storage) { + return _impl.storage + _impl.capacity - 1; + } else { + return _impl.begin - 1; + } +} + +template +inline +void +circular_buffer::post_push_front(T* p) { + _impl.begin = p; + ++_impl.size; +} + +template +inline +T* +circular_buffer::pre_push_back() { + maybe_expand(); + return _impl.end; +} + +template +inline +void +circular_buffer::post_push_back(T* p) { + if (++p == _impl.storage + _impl.capacity) { + p = _impl.storage; + } + _impl.end = p; + ++_impl.size; +} + +template +inline +void +circular_buffer::push_front(const T& data) { + auto p = pre_push_front(); + _impl.construct(p, data); + post_push_front(p); +} + +template +inline +void +circular_buffer::push_front(T&& data) { + auto p = pre_push_front(); + _impl.construct(p, std::move(data)); + post_push_front(p); +} + +template +template +inline +void +circular_buffer::emplace_front(Args... args) { + auto p = pre_push_front(); + _impl.construct(p, std::forward(args)...); + post_push_front(p); +} + +template +inline +void +circular_buffer::push_back(const T& data) { + auto p = pre_push_back(); + _impl.construct(p, data); + post_push_back(p); +} + +template +inline +void +circular_buffer::push_back(T&& data) { + auto p = pre_push_back(); + _impl.construct(p, std::move(data)); + post_push_back(p); +} + +template +template +inline +void +circular_buffer::emplace_back(Args... args) { + auto p = pre_push_back(); + _impl.construct(p, std::forward(args)...); + post_push_back(p); +} + +template +inline +T& +circular_buffer::front() { + return *_impl.begin; +} + +template +inline +T& +circular_buffer::back() { + if (_impl.end == _impl.storage) { + return *(_impl.storage + _impl.capacity - 1); + } else { + return *_impl.end; + } +} + +template +inline +void +circular_buffer::pop_front() { + ++_impl.begin; + if (_impl.begin == _impl.storage + _impl.capacity) { + _impl.begin = _impl.storage; + } + --_impl.size; +} + +template +inline +void +circular_buffer::pop_back() { + if (_impl.end == _impl.begin) { + _impl.end = _impl.storage + _impl.capacity; + } + --_impl.end; + --_impl.size; +} + +#endif /* CIRCULAR_BUFFER_HH_ */