xo-alloc2: ++ documentation + threshold size for THP feature
This commit is contained in:
parent
992e5a5a8c
commit
289751d3fd
18 changed files with 642 additions and 192 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
///@}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
34
include/xo/alloc2/print.hpp
Normal file
34
include/xo/alloc2/print.hpp
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue