259 lines
11 KiB
C++
259 lines
11 KiB
C++
/** @file DCircularBuffer.hpp
|
|
*
|
|
* @author Roland Conybeare, Jan 2026
|
|
**/
|
|
|
|
#include "CircularBufferConfig.hpp"
|
|
#include "DArenaVector.hpp"
|
|
#include "hashmap/verify_policy.hpp"
|
|
#include "span.hpp"
|
|
#include <cstdint>
|
|
|
|
namespace xo {
|
|
namespace mm {
|
|
/** @class DCircularBuffer
|
|
*
|
|
* @brief high performance vm-aware circular buffer
|
|
*
|
|
* Circular buffer implementation with parsing-friendly performance features.
|
|
* - generalization of DArena.
|
|
* Like DArena, maps superpages as needed.
|
|
* Unlike DArena memory at the beginning of reserved range can be unmapped.
|
|
* - allows address range >> physical range
|
|
* - admits "Cheney on the MTA" strategy.
|
|
* May be feasible to reserve a lifetime address range (say 1TB)
|
|
* as long as buffer only every maps a subrange that fits in physical memory.
|
|
* - zero copy support for parsing / protocol trnaslation:
|
|
* provides capture/release semantics for a fixed number
|
|
* of remembered spans. Will never unmap memory for a remembered span,
|
|
* until that span is released.
|
|
* - automatically resets to beginning of reserved range
|
|
* whenever occupied range is empty
|
|
**/
|
|
struct DCircularBuffer {
|
|
public:
|
|
/** @defgroup mm-circularbuffer-types CircularBuffer type traits **/
|
|
///@{
|
|
|
|
/** an amount of memory **/
|
|
using size_type = std::size_t;
|
|
using byte = std::byte;
|
|
/** a contiguous addres range **/
|
|
using span_type = span<char>;
|
|
using const_span_type = span<const char>;
|
|
|
|
///@}
|
|
|
|
public:
|
|
/** @defgroup mm-cicrularbuffer-ctors CircularBuffer constructors **/
|
|
///@{
|
|
|
|
/** contruct instance
|
|
* @p config circular buffer configuration
|
|
* @p page_z o/s page size (via getpagesize())
|
|
* @p buffer_align_z alignment for buffer memory
|
|
* @p reserved_range reserved virtual address range
|
|
**/
|
|
DCircularBuffer(const CircularBufferConfig & config,
|
|
size_type page_z,
|
|
size_type buffer_align_z,
|
|
span_type reserved_range);
|
|
#ifdef NOT_YET
|
|
/** constructor */
|
|
DCircularBuffer(const CircularBufferConfig & config);
|
|
#endif
|
|
/** non-copyable **/
|
|
DCircularBuffer(const DCircularBuffer & other) = delete;
|
|
/** move ctor **/
|
|
DCircularBuffer(DCircularBuffer && other);
|
|
|
|
/**
|
|
* allocate virtual memory address (uncommitted!) for circular buffer
|
|
* with configuration @p config.
|
|
**/
|
|
static DCircularBuffer map(const CircularBufferConfig & config);
|
|
|
|
///@}
|
|
|
|
/** @defgroup mm-circularbuffer-const-methods CircularBuffer const methods **/
|
|
///@{
|
|
|
|
const_span_type reserved_range() const noexcept { return reserved_range_; }
|
|
const_span_type mapped_range() const noexcept { return mapped_range_; }
|
|
const_span_type occupied_range() const noexcept { return occupied_range_; }
|
|
const_span_type input_range() const noexcept { return input_range_; }
|
|
|
|
/** verify DCircularBuffer invariants.
|
|
* Act on failure according to policy @p p
|
|
* (combination of throw|log bits)
|
|
*
|
|
* verify invariants:
|
|
* CB1: mapped_range_ is subrange of reserved_range_
|
|
* CB2: occupied_range_ is subrange of mapped_range_
|
|
* CB3: each remembered_spans_[i] is subrange of occupied_range_
|
|
* CB4: buffer_align_z_ > 0 when buffer is mapped
|
|
* CB5: reserved_range_.lo() aligned on buffer_align_z_ boundary
|
|
**/
|
|
bool verify_ok(verify_policy p = verify_policy::throw_only()) const;
|
|
|
|
///@}
|
|
|
|
/** @defgroup mm-circularbuffer-nonconst-methods CircularBuffer non-const methods **/
|
|
///@{
|
|
|
|
span_type input_range() noexcept { return input_range_; }
|
|
|
|
/** copy memory in span @p r into buffer starting at the end of
|
|
* @ref occupied_range_. Map new physical memory as needed.
|
|
* On success returns empty suffix of @p r.
|
|
* If buffer memory exhausted, may copy a prefix of @p r.
|
|
* In that case returns the remaining suffix of @p r.
|
|
**/
|
|
const_span_type append(const_span_type r);
|
|
|
|
/** DMA version of @ref append_span : get mapped span A at which
|
|
* buffer will receive new content. Upstream may write into
|
|
* A. It must then coordinate with buffer by calling
|
|
* @ref report_append(P) for some prefix P of A
|
|
*
|
|
* Example:
|
|
* @code
|
|
* CircularBuffer buf = ...;
|
|
* constexpr size_type z = 64*1024;
|
|
* auto span = buf.get_append_span(z);
|
|
* ssize_t nr = read(FD, span.lo(), span.size());
|
|
* if (nr > 0)
|
|
* buf.report_append(span.prefix(nr));
|
|
* @endcode
|
|
**/
|
|
span_type get_append_span(size_type desired_z);
|
|
|
|
/** update bookkeeping as if caller had invoked append(r);
|
|
* however caller has already written to mapped memory
|
|
* after using get_append_span(); so omit copy
|
|
**/
|
|
void report_append(span_type r);
|
|
|
|
/** consume span (or prefix thereof) previously obtained from @ref occupied_range()
|
|
* Caller represents that it won't need to read this memory again
|
|
* unless overlaps with a pinned span.
|
|
**/
|
|
void consume(const_span_type input);
|
|
|
|
/** pin memory range @p r. circular buffer will not touch
|
|
* addresses that appear in any pinned range.
|
|
* use to
|
|
**/
|
|
void pin_range(span_type r);
|
|
|
|
/** unwind a previous pin_range call on range @p r.
|
|
* both start and end or @p r should exactly match a pinned range.
|
|
**/
|
|
void unpin_range(span_type r);
|
|
|
|
///@}
|
|
|
|
private:
|
|
|
|
/** @defgroup mm-circularbuffer-private-methods CircularBuffer non-const methods **/
|
|
///@{
|
|
|
|
/** expand hi end of mapped memory range to at least @p hi.
|
|
*
|
|
* Require: @p hi < @ref reserved_range_.hi
|
|
**/
|
|
bool _expand_to(char * hi);
|
|
|
|
/** shrink occupied rnage to the smallest contiguous range that contains both:
|
|
* all of .input_range_, and all pinned ranges in .pinned_spans_
|
|
**/
|
|
void _shrink_occupied_to_fit();
|
|
|
|
/** check for edge condition in which there are no pinned ranges. **/
|
|
void _check_reset_map_start();
|
|
|
|
///@}
|
|
|
|
private:
|
|
/** @defgroup mm-circularbuffer-instance-vars CircularBuffer member variables **/
|
|
///@{
|
|
|
|
/* memory layout
|
|
*
|
|
* reserved_range_ : entire address range owned by buffer (may be huge, e.g., 1TB)
|
|
* mapped_range_ : subrange backed by physical memory (fits in RAM)
|
|
* occupied_range_ : subrange currently containing data
|
|
* input_range_ : subrange containing unread input
|
|
* pinned_spans_ : pinned subranges within occupied (prevents alteration or unmap)
|
|
*
|
|
* <------------------- .reserved_range --------------------->
|
|
* . <------------- .mapped_range -------------> .
|
|
* . . <----- .occupied_range -----> . .
|
|
* . . . <- .input_range -----> . .
|
|
* . . . . . . .
|
|
* ........------XXXXXXXIIIIIIIIIIIIIIIIIIIIII--------........
|
|
* pp ppp pp
|
|
* Legend:
|
|
* [.] reserved : uncommitted memory. may be huge (e.g. 1TB)
|
|
* [-] mapped : range backed by physical memory
|
|
* [X] consumed : preserved until last overlapping pin removed
|
|
* [I] input : unread content, waiting to be read
|
|
* [p] pinned : pinned memory will not be altered (let alone unmapped)
|
|
*
|
|
* Invariants:
|
|
* - .input_range <= .occupied_range <= .mapped_range <= .reserved_range
|
|
* - mapped_range_ cannot shrink to exclude any portion of a pinned span
|
|
*/
|
|
|
|
/** buffer configuration **/
|
|
CircularBufferConfig config_;
|
|
|
|
/** size of a VM page (obtained automatically via getpagesize()). 4k on ubuntu. 16k on osx **/
|
|
size_type page_z_;
|
|
|
|
/** alignment for buffer address range.
|
|
* In practice will be either page_z_ or config_.hugepage_z_
|
|
**/
|
|
size_type buffer_align_z_;
|
|
|
|
/** Circular buffer owns address range defined by this span.
|
|
* Aligned on @ref buffer_align_z_.
|
|
* Always a whole number of @ref page_z_ or @ref config_.hugepage_z_
|
|
**/
|
|
span_type reserved_range_;
|
|
|
|
/** buffer owns memory defined by this span.
|
|
* Always a subrange of reserved_range
|
|
* These addresses backed by physical memory.
|
|
* Always a whole number of @ref page_z_ or @ref config_.hugepage_z_
|
|
**/
|
|
span_type mapped_range_;
|
|
|
|
/** currently occupied buffer memory.
|
|
* Always a subrange of @ref mapped_range_
|
|
**/
|
|
span_type occupied_range_;
|
|
|
|
/** portion of occupied buffer memory waiting to be read.
|
|
* Always represents a subspan of @ref occupied_range_, with the same
|
|
* hi endpoint.
|
|
* conversely @ref consume shrinks @ref input_range_ by increasing its lo endpoint.
|
|
**/
|
|
span_type input_range_;
|
|
|
|
/** remembered spans. For anticipated use cases expect one vm page sufficient.
|
|
* Spans in this vector always represent subranges of @ref occupied_range_
|
|
*
|
|
* @ref pinned_spans_ is confined to @ref occupied_range_.
|
|
* (In particular it's *not* confined to @ref input_range_)
|
|
*
|
|
* sorted on increasing span.lo()
|
|
**/
|
|
DArenaVector<span_type> pinned_spans_;
|
|
|
|
///@}
|
|
};
|
|
}
|
|
} /*namespace xo*/
|
|
|
|
/* end DCircularBuffer.hpp */
|