xo-alloc2: ++ documentation + threshold size for THP feature

This commit is contained in:
Roland Conybeare 2025-12-23 21:06:38 -05:00
commit 289751d3fd
18 changed files with 642 additions and 192 deletions

View file

@ -14,7 +14,7 @@ namespace xo {
/** sentinel **/
invalid = -1,
/** not an error **/
none,
ok,
/** reserved size exhauged **/
reserve_exhausted,
/** unable to commit (i.e. mprotect failure) **/
@ -57,8 +57,10 @@ namespace xo {
committed_z_{com_z},
reserved_z_{rsv_z} {}
static const char * error_description(error x);
/** error code **/
error error_ = error::none;
error error_ = error::ok;
/** sequence# of this error.
* Each error event within an allocator gets next sequence number

View file

@ -18,10 +18,18 @@ namespace xo {
*
**/
struct AllocInfo {
/** @defgroup mm-allocinfo-traits **/
///@{
using size_type = AllocHeader::size_type;
using byte = std::byte;
using span_type = std::pair<const byte *, const byte *>;
///@}
/** @defgroup mm-allocinfo-ctors **/
///@{
AllocInfo(const AllocHeaderConfig * p_cfg,
const byte * p_guard_lo,
const AllocHeader * p_hdr,
@ -39,6 +47,11 @@ namespace xo {
return AllocInfo(p_cfg, nullptr, nullptr, nullptr);
}
///@}
/** @defgroup mm-allocinfo-methods **/
///@{
/** true for non-sentinel AllocInfo instance **/
bool is_valid() const { return (p_config_ != nullptr) && (p_header_ != nullptr); }
@ -59,10 +72,17 @@ namespace xo {
/** Value (fixed test pattern) of guard byte **/
char guard_byte() const noexcept { return p_config_->guard_byte_; }
///@}
/** @defgroup mm-allocinfo-instance-vars **/
///@{
const AllocHeaderConfig * p_config_ = nullptr;
const byte * p_guard_lo_ = nullptr;
const AllocHeader * p_header_ = nullptr;
const byte * p_guard_hi_ = nullptr;
///@}
};
} /*namespace mm*/
} /*namespace xo*/

View file

@ -25,6 +25,9 @@ namespace xo {
/** @class AAllocator
* @brief Abstract facet for allocation
*
* Methods take a opaque data pointer.
* Implementations of AAllocator will downcast to a
* to some specific representation.
**/
struct AAllocator {
/** @defgroup mm-allocator-type-traits allocator type traits **/
@ -56,17 +59,16 @@ namespace xo {
/** RTTI: unique id# for actual runtime data representation **/
virtual int32_t _typeseq() const noexcept = 0;
/** optional name for allocator @p d
* Labeling, for diagnostics.
/** optional name for allocator @p d .
* Allows labeling allocators, for diagnostics/instrumentation.
**/
virtual std::string_view name(Copaque d) const noexcept = 0;
/** reserved size in bytes for allocator @p d.
* Includes committed + uncommitted memory.
* Cannot be increased.
**/
virtual size_type reserved(Copaque d) const noexcept = 0;
/** Synonym for @ref committed.
* Can increase on @ref alloc
* Can increase automatically on @ref alloc
**/
virtual size_type size(Copaque d) const noexcept = 0;
/** committed size (physical addresses obtained)
@ -74,14 +76,22 @@ namespace xo {
* @ref alloc may auto-increase this
**/
virtual size_type committed(Copaque d) const noexcept = 0;
/** unallocated (but committed) size in bytes for allocator @p d **/
/** unallocated (but committed) size in bytes for allocator @p d.
* An alloc request up to this size (including guard / header)
* is guaranteed to succeed.
* An alloc request of more than this size may still succeed,
* if allocator can automatically extend committed memory.
* This is the case for the @ref xo::mm::DArena allocator
**/
virtual size_type available(Copaque d) const noexcept = 0;
/** allocated (i.e. in-use) amount in bytes for allocator @p d **/
/** allocated (i.e. currently in-use) amount in bytes for allocator @p d.
* Includes alloc headers and guard regions
**/
virtual size_type allocated(Copaque d) const noexcept = 0;
/** true iff allocator @p d is responsible for memory at address @p p.
**/
virtual bool contains(Copaque d, const void * p) const noexcept = 0;
/** report last error **/
/** report details of last error for allocator @p d. **/
virtual AllocError last_error(Copaque d) const noexcept = 0;
/** fetch alloc info: given memory @p mem previously obtained
* from {@ref alloc, @ref super_alloc}, get {tseq, age, size} details
@ -90,28 +100,23 @@ namespace xo {
* Non-const @p d because may stash error details
**/
virtual AllocInfo alloc_info(Copaque d, value_type mem) const noexcept = 0;
/** Ideally we want to control allocator for iterator here.
* Awkward to supply to compiler since we don't have obj<AAllocator> yet.
* OTOH iteration over allocs is a super-niche feature.
*
* Rejected alternatives:
* - put begin/end in separate interface. e.g. extend AAllocator
* - layer of indirection: begin/end return iterator factory.
* Then allocator can be passed to iterator factory separately.
* Helps because factory can be static
* - abandon allocator support in this case. Instead will need to
* reinstate uvt<AAllocIterator> (unique variant), use heap
*
* @p mm is allocator for resulting iterator range
/**
* Create an iterator range for allocator @p d.
* An iterator range has begin and end methods, so supports c++ range iteration.
* Memory for iterator state will be obtained from @p mm.
**/
virtual range_type alloc_range(Copaque d, DArena & mm) const noexcept = 0;
/** expand committed space in arena @p d
* to size at least @p z
* In practice will round up to a multiple of hugepage size (2MB)
* to size at least @p z.
* In practice will round up to a multiple of page size (4K) or hugepage size (2MB)
* depending on configuration.
**/
virtual bool expand(Opaque d, std::size_t z) const noexcept = 0;
/** allocate @p z bytes of memory from allocator @p d. **/
/** attempt to allocate @p z bytes of memory from allocator @p d.
* If allocation fails returns nullptr. In this case error details may be retrieved
* using last error
**/
virtual value_type alloc(Opaque d, size_type z) const = 0;
/** like @ref alloc, but follow with one or more consecutive
* @ref sub_alloc() calls. This sequence of allocs will share
@ -125,9 +130,11 @@ namespace xo {
* zero @p z
**/
virtual value_type sub_alloc(Opaque d, size_type z, bool complete_flag) const = 0;
/** reset allocator @p d to empty state **/
/** reset allocator @p d to empty state. **/
virtual void clear(Opaque d) const = 0;
/** destruct allocator @p d **/
/** Destruct allocator @p d.
* Releases allocator memory to operating system.
**/
virtual void destruct_data(Opaque d) const = 0;
///@}

View file

@ -47,9 +47,15 @@ namespace xo {
/** @brief mode argument for @ref _alloc **/
enum class alloc_mode : uint8_t {
/** ordinary alloc. Most common mode **/
standard,
/** begin a sequence of suballocs that share a single alloc header **/
super,
/** make a subsidiary allocation on behalf of a preceding super alloc.
* Will be followed by at least one more suballoc call.
**/
sub_incomplete,
/** make a subsidiary allocation that completes preceding super alloc. **/
sub_complete,
};
@ -64,7 +70,11 @@ namespace xo {
/** null ctor **/
DArena() = default;
/** ctor from already-mapped (but not committed) address range **/
DArena(const ArenaConfig & cfg, size_type page_z, value_type lo, value_type hi);
DArena(const ArenaConfig & cfg,
size_type page_z,
size_type arena_align_z,
value_type lo,
value_type hi);
/** DArena is not copyable **/
DArena(const DArena & other) = delete;
/** move ctor **/
@ -80,18 +90,49 @@ namespace xo {
/** @defgroup mm-arena-methods **/
///@{
/** Reserved memory, in bytes. This is the maximum size of this arena. **/
size_type reserved() const noexcept { return hi_ - lo_; }
/** Allocated memory in bytes: memory consumed by allocs from this arena,
* including administrative overhead (alloc headers + guard bytes)
**/
size_type allocated() const noexcept { return free_ - lo_; }
/** Committed memory in bytes: amount of memory actually backed by physical memory **/
size_type committed() const noexcept { return committed_z_; }
/** Available committed memory.
* This is the amount of memory guaranteed to be usable for future allocs from this arena.
**/
size_type available() const noexcept { return limit_ - free_; }
/** True iff address @p addr is owned by this arena,
* i.e. falls within [@ref lo_, @ref hi_)
**/
bool contains(const void * addr) const noexcept { return (lo_ <= addr) && (addr < hi_); }
/** obtain uncommitted contiguous memory range comprising
* a whole multiple of @p hugepage_z bytes, of at least size @p req_z,
* aligned on a @p hugepage_z boundary
* a whole multiple of @p align_z bytes, of at least size @p req_z,
* aligned on a @p align_z boundary. Uncommitted memory is not (yet)
* backed by physical memory.
*
* If @p enable_hugepage_flag is true and THP
* (transparent huge pages) are available, use THP for arena memory.
* This relieves TLB and page table memory when @p req_z is a lot larger than
* page size (likely 4KB). Cost is that arena will consum physical memory in unit
* of @p align_z. Arena may waste up to @p align_z bytes of memory as a result.
*
* If @p enable_hugepage_flag is true, @p align_z should be huge page size
* (probably 2MB) for optimal performance.
*
* At present the THP feature is not supported on OSX.
* May be supportable through mach_vm_allocate().
*
* Note that we reject MAP_HUGETLB|MAP_HUGE_2MB flags to mmap here,
* since requires previously-reserved memory in /proc/sys/vm/nr_hugepages.
*
* @return pair giving reserved memory address range [lo,hi)
**/
static range_type map_aligned_range(size_type req_z, size_type hugepage_z);
static range_type map_aligned_range(size_type req_z,
size_type align_z,
bool enable_hugepage_flag);
/** true if arena is mapped i.e. has a reserved address range **/
bool is_mapped() const noexcept { return (lo_ != nullptr) && (hi_ != nullptr); }
@ -177,6 +218,9 @@ namespace xo {
/** size of a VM page (obtained automatically via getpagesize()). Likely 4k **/
size_type page_z_ = 0;
/** alignment for this arena. In practice will be either page_z_ or cfg.hugepage_z_ **/
size_type arena_align_z_ = 0;
/** arena owns memory in range [@ref lo_, @ref hi_)
**/
std::byte * lo_ = nullptr;

View file

@ -0,0 +1,34 @@
/** @file print.hpp
*
* @author Roland Conybeare, Dec 2025
**/
#pragma once
#include "AllocError.hpp"
#include <xo/indentlog/print/tag.hpp>
#include <iostream>
namespace xo {
namespace mm {
inline std::ostream &
operator<<(std::ostream & os, const error & x) {
os << AllocError::error_description(x);
return os;
}
inline std::ostream &
operator<<(std::ostream & os, const AllocError & x) {
os << "<AllocError"
<< xtag("error", x.error_)
<< xtag("seq", x.error_seq_)
<< xtag("req_z", x.request_z_)
<< xtag("commit_z", x.committed_z_)
<< xtag("resv_z", x.reserved_z_)
<< ">";
return os;
}
}
}
/* end print.hpp */