xo-alloc2: work on fomo Arena
This commit is contained in:
parent
19e612dd14
commit
a69158ab32
13 changed files with 507 additions and 8 deletions
|
|
@ -19,7 +19,7 @@ add_definitions(${PROJECT_CXX_FLAGS})
|
|||
# ----------------------------------------------------------------
|
||||
|
||||
# must complete definition of expression lib before configuring examples
|
||||
#add_subdirectory(src/alloc)
|
||||
add_subdirectory(src/alloc2)
|
||||
add_subdirectory(utest)
|
||||
#xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
include(CMakeFindDependencyMacro)
|
||||
#find_dependency(indentlog)
|
||||
#find_dependency(xo_flatstring)
|
||||
find_dependency(xo_facet)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
|
|
|
|||
91
include/xo/alloc2/AAllocator.hpp
Normal file
91
include/xo/alloc2/AAllocator.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/** @file AAllocator.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xo/facet/facet_implementation.hpp"
|
||||
#include "xo/facet/typeseq.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
using Copaque = const void *;
|
||||
using Opaque = void *;
|
||||
|
||||
|
||||
/** Abstract facet for allocation
|
||||
*
|
||||
* <----------------------------size-------------------------->
|
||||
* <------------committed-----------><-------uncommitted------>
|
||||
* <--allocated-->
|
||||
*
|
||||
* XXXXXXXXXXXXXXX___________________..........................
|
||||
*
|
||||
* allocated: in use
|
||||
* committed: physical memory obtained
|
||||
* uncommitted: mapped in virtual memory, not backed by memory
|
||||
**/
|
||||
struct AAllocator {
|
||||
/** RTTI: unique id# for actual runtime data repr **/
|
||||
virtual int32_t _typeseq() = 0;
|
||||
/** optional name for this allocator.
|
||||
* Labeling, for diagnostics.
|
||||
**/
|
||||
virtual const std::string & name(Copaque d) = 0;
|
||||
/** allocator size in bytes (up to reserved limit)
|
||||
* includes allocated and uncomitted memory
|
||||
**/
|
||||
virtual std::size_t size(Copaque d) = 0;
|
||||
/** committed size (physical addresses obtained)
|
||||
**/
|
||||
virtual std::size_t committed(Copaque d) = 0;
|
||||
/** true iff pointer @p in range of this allocator
|
||||
**/
|
||||
virtual bool contains(Copaque d, const void * p) = 0;
|
||||
|
||||
/** allocate @p z bytes of memory. **/
|
||||
virtual std::byte * alloc(Opaque d, std::size_t z) = 0;
|
||||
/** reset allocator to empty state **/
|
||||
virtual void clear(Opaque d) = 0;
|
||||
/** **/
|
||||
virtual void destruct_data(Opaque d) = 0;
|
||||
};
|
||||
|
||||
template <typename DRepr>
|
||||
struct IAllocator_Impl;
|
||||
|
||||
template <typename DRepr>
|
||||
struct IAllocator_Xfer : public AAllocator {
|
||||
// parallel interface to AAllocator, with specific data type
|
||||
using Impl = IAllocator_Impl<DRepr>;
|
||||
|
||||
static const DRepr & _dcast(Copaque d) { return *(const DRepr *)d; }
|
||||
|
||||
// from AAllocator
|
||||
int32_t _typeseq() override { return s_typeseq; }
|
||||
const std::string & name(Copaque d) override { return Impl::name(_dcast(d)); }
|
||||
std::size_t size(Copaque d) override { return Impl::size(*(DRepr*)d); }
|
||||
std::size_t committed(Copaque d) override { return Impl::committed(*(DRepr*)d); }
|
||||
bool contains(Copaque d, const void * p) override { return Impl::contains(*(DRepr*)d, p); }
|
||||
|
||||
std::byte * alloc(Opaque d, std::size_t z) override { return Impl::alloc(*(DRepr*)d, z); }
|
||||
void clear(Opaque d) override { return Impl::clear(*(DRepr*)d); }
|
||||
void destruct_data(Opaque d) override { return Impl::destruct_data(*(DRepr*)d); }
|
||||
|
||||
static int32_t s_typeseq;
|
||||
static bool _valid;
|
||||
};
|
||||
|
||||
template <typename DRepr>
|
||||
int32_t
|
||||
IAllocator_Xfer<DRepr>::s_typeseq = facet::typeseq::id<DRepr>();
|
||||
|
||||
template <typename DRepr>
|
||||
bool
|
||||
IAllocator_Xfer<DRepr>::_valid = facet::valid_facet_implementation<AAllocator, IAllocator_Xfer>();
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end AAllocator.hpp */
|
||||
82
include/xo/alloc2/DArena.hpp
Normal file
82
include/xo/alloc2/DArena.hpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/** @file DArena.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
|
||||
/** ArenaConfig
|
||||
**/
|
||||
struct ArenaConfig {
|
||||
/** optional name, for diagnostics **/
|
||||
std::string name_;
|
||||
/** arena size -- hard max = reserved virtual memory **/
|
||||
std::size_t size_;
|
||||
/** hugepage size -- using huge pages relieves some TLB pressure
|
||||
* (provided you use their full extent :)
|
||||
**/
|
||||
std::size_t hugepage_z_ = 2 * 1024 * 1024;
|
||||
/** true to enable debug logging **/
|
||||
bool debug_flag_ = false;
|
||||
};
|
||||
|
||||
/** Arena allocator state
|
||||
*
|
||||
* <----------------------------size-------------------------->
|
||||
* <------------committed-----------><-------uncommitted------>
|
||||
* <--allocated-->
|
||||
*
|
||||
* XXXXXXXXXXXXXXX___________________..........................
|
||||
*
|
||||
* allocated: in use
|
||||
* committed: physical memory obtained
|
||||
* uncommitted: mapped in virtual memory, not backed by memory
|
||||
**/
|
||||
struct DArena {
|
||||
/** [lo, hi) already-mapped address range **/
|
||||
DArena(const ArenaConfig & cfg,
|
||||
std::byte * lo,
|
||||
std::byte * hi);
|
||||
|
||||
~DArena();
|
||||
|
||||
ArenaConfig config_;
|
||||
|
||||
/** size of a VM page (via getpagesize()). Likely 4k **/
|
||||
std::size_t page_z_ = 0;
|
||||
|
||||
/** arena owns memory in range [@ref lo_, @ref hi_)
|
||||
**/
|
||||
std::byte * lo_ = nullptr;
|
||||
|
||||
/** prefix of this size is committed.
|
||||
* Remainder mapped but uncommitted.
|
||||
**/
|
||||
std::size_t committed_z_ = 0;
|
||||
|
||||
/** free pointer.
|
||||
* Memory in range [@ref lo_, @ref free_) current in use
|
||||
**/
|
||||
std::byte * free_ = nullptr;
|
||||
|
||||
/** soft limit; end of committed virtual memory
|
||||
* Memory in range [@ref lo_, @ref limit_) is committed
|
||||
* (backed by physical memory)
|
||||
**/
|
||||
std::byte * limit_ = nullptr;
|
||||
|
||||
/** hard limit; end of reserved virtual memory
|
||||
* Memory in range [@ref limit_, @ref hi_) is uncommitted
|
||||
**/
|
||||
std::byte * hi_ = nullptr;
|
||||
};
|
||||
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end DArena.hpp */
|
||||
30
include/xo/alloc2/IAllocator_DArena.hpp
Normal file
30
include/xo/alloc2/IAllocator_DArena.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/** @file IAllocator_DArena.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "AAllocator.hpp"
|
||||
#include "DArena.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
|
||||
template <>
|
||||
struct IAllocator_Impl<DArena> {
|
||||
static const std::string & name(const DArena & s) {
|
||||
return s.name_;
|
||||
}
|
||||
|
||||
static std::size_t size(const DArena & s);
|
||||
static std::size_t committed(const DArena & s);
|
||||
static bool contains(const DArena & s, const void * p);
|
||||
static std::byte * alloc(const DArena & s, std::size_t z);
|
||||
static void clear(DArena & s);
|
||||
static void destruct_data(DArena & s);
|
||||
};
|
||||
|
||||
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end IAllocator_DArena.hpp */
|
||||
57
include/xo/alloc2/padding.hpp
Normal file
57
include/xo/alloc2/padding.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/** @file padding.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
|
||||
struct padding {
|
||||
/** word size for alignment**/
|
||||
static constexpr std::size_t c_alloc_alignment = sizeof(std::uintptr_t);
|
||||
|
||||
/** how much to add to @p z to get a multiple of
|
||||
* @ref c_alloc_alignment
|
||||
**/
|
||||
static inline std::size_t alloc_padding(std::size_t z,
|
||||
std::size_t align = c_alloc_alignment)
|
||||
{
|
||||
|
||||
/* round up to multiple of c_bpw, but map 0 -> 0
|
||||
* (table assuming c_bpw==8)
|
||||
*
|
||||
* z%c_bpw dz
|
||||
* ------------
|
||||
* 0 0
|
||||
* 1 7
|
||||
* 2 6
|
||||
* .. ..
|
||||
* 7 1
|
||||
*/
|
||||
std::size_t dz = (align - (z % align)) % align;
|
||||
|
||||
z += dz;
|
||||
|
||||
return dz;
|
||||
}
|
||||
|
||||
/** @p z rounded up to an exact multiple
|
||||
* of @ref c_alloc_alignment
|
||||
**/
|
||||
static inline
|
||||
std::size_t with_padding(std::size_t z,
|
||||
std::size_t align)
|
||||
{
|
||||
return z + alloc_padding(z, align);
|
||||
}
|
||||
};
|
||||
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end padding.hpp */
|
||||
0
src/DArena.cpp
Normal file
0
src/DArena.cpp
Normal file
11
src/alloc2/CMakeLists.txt
Normal file
11
src/alloc2/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# alloc2/CMakeLists.txt
|
||||
|
||||
set(SELF_LIB xo_alloc2)
|
||||
set(SELF_SRCS
|
||||
DArena.cpp
|
||||
IAllocator_DArena.cpp
|
||||
)
|
||||
|
||||
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
|
||||
# note: deps here must also appear in cmake/xo_alloc2Config.cmake.in
|
||||
xo_dependency(${SELF_LIB} xo_facet)
|
||||
149
src/alloc2/DArena.cpp
Normal file
149
src/alloc2/DArena.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/** @file DArena.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "xo/alloc2/AAllocator.hpp"
|
||||
#include "xo/alloc2/DArena.hpp"
|
||||
#include "xo/alloc2/padding.hpp"
|
||||
#include <cassert>
|
||||
#include <sys/mman.h> // for ::munmap()
|
||||
#include <unistd.h> // for ::getpagesize()
|
||||
|
||||
namespace xo {
|
||||
using std::byte;
|
||||
|
||||
namespace mm {
|
||||
|
||||
DArena::DArena(const ArenaConfig & cfg,
|
||||
std::byte * lo,
|
||||
std::byte * hi
|
||||
)
|
||||
{
|
||||
//scope log(XO_DEBUG(debug_flag), xtag("name", name));
|
||||
|
||||
this->page_z_ = getpagesize();
|
||||
|
||||
// 1. need k pagetable entries where k is lub {k | k * .page_z >= z}
|
||||
// 2. base will be aligned with .page_z but likely not with .hugepage_z
|
||||
// 3. bad to have misalignment, because misaligned {prefix, suffix} of [base, base+z)
|
||||
// will use 4k pages instead of 2mb pages
|
||||
//
|
||||
// strategy:
|
||||
// 4. round up z to multiple of hugepage_z_
|
||||
// 5. over-request so reserved range contains an aligned subrange of size z
|
||||
// 6. unmap misaligned prefix
|
||||
// 7. unmap misaligned suffix.
|
||||
// 8. enable huge pages for now-aligned remainder of reserved range
|
||||
//
|
||||
// Z. note: rejecting inferior MAP_HUGETLB|MAP_HUGE_2MB flags on ::mmap here:
|
||||
// Za. requires previously-reserved memory in /proc/sys/vm/nr_hugepages
|
||||
// Zb. reserved pages permenently resident in RAM, never swapped
|
||||
// Zc. memory cost incurred even if no application is using said pages
|
||||
|
||||
std::size_t z = cfg.size_;
|
||||
|
||||
z = padding::with_padding(z, config_.hugepage_z_); // 4.
|
||||
|
||||
// 5.
|
||||
byte * base = reinterpret_cast<byte *>(
|
||||
::mmap(nullptr,
|
||||
z + config_.hugepage_z_,
|
||||
PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1, 0));
|
||||
|
||||
#ifdef NOT_YET
|
||||
log && log("acquired memory [lo,hi) using mmap",
|
||||
xtag("lo", base),
|
||||
xtag("z", z),
|
||||
xtag("hi", reinterpret_cast<byte *>(base) + z));
|
||||
#endif
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
assert(false);
|
||||
#ifdef NOPE
|
||||
throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed",
|
||||
xtag("size", z)));
|
||||
#endif
|
||||
}
|
||||
|
||||
byte * aligned_base = reinterpret_cast<byte *>
|
||||
(padding::with_padding(reinterpret_cast<size_t>(base),
|
||||
config_.hugepage_z_));
|
||||
|
||||
assert(reinterpret_cast<size_t>(aligned_base) % config_.hugepage_z_ == 0);
|
||||
assert(aligned_base >= base);
|
||||
assert(aligned_base < base + config_.hugepage_z_);
|
||||
|
||||
if (base < aligned_base) {
|
||||
size_t prefix = aligned_base - base;
|
||||
|
||||
::munmap(base, prefix); // 6.
|
||||
}
|
||||
|
||||
byte * aligned_hi = aligned_base + z;
|
||||
byte * hi = base + z + config_.hugepage_z_;
|
||||
|
||||
if (aligned_hi < hi) {
|
||||
size_t suffix = hi - aligned_hi;
|
||||
|
||||
::munmap(aligned_hi, suffix); // 7.
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/** opt-in to huge pages, provided they're available.
|
||||
* otherwise fallback gracefully
|
||||
**/
|
||||
::madvise(aligned_base, z, MADV_HUGEPAGE); // 8.
|
||||
#endif
|
||||
// TODO: for OSX -> need something else here.
|
||||
// MAP_ALIGNED_SUPER with mmap() and/or
|
||||
// use mach_vm_allocate()
|
||||
//
|
||||
|
||||
this->lo_ = aligned_base;
|
||||
this->committed_z_ = 0;
|
||||
//this->checkpoint_ = lo_;
|
||||
this->free_ = lo_;
|
||||
this->limit_ = lo_;
|
||||
this->hi_ = lo_ + z;
|
||||
|
||||
if (!lo_) {
|
||||
assert(false);
|
||||
#ifdef NOPE
|
||||
throw std::runtime_error(tostr("ArenaAlloc: allocation failed",
|
||||
xtag("size", z)));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef NOPE
|
||||
log && log(xtag("lo", (void*)lo_),
|
||||
xtag("page_z", page_z_),
|
||||
xtag("hugepage_z", hugepage_z_));
|
||||
#endif
|
||||
}
|
||||
|
||||
DArena::~DArena()
|
||||
{
|
||||
if (lo_) {
|
||||
//log && log("unmap [lo,hi)",
|
||||
// xtag("lo", lo_),
|
||||
// xtag("z", hi_ - lo_),
|
||||
// xtag("hi", hi_));
|
||||
|
||||
::munmap(lo_, hi_ - lo_);
|
||||
}
|
||||
|
||||
// hygiene
|
||||
lo_ = nullptr;
|
||||
committed_z_ = 0;
|
||||
// checkpoint_ = nullptr;
|
||||
free_ = nullptr;
|
||||
limit_ = nullptr;
|
||||
hi_ = nullptr;
|
||||
}
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end DArena.cpp */
|
||||
56
src/alloc2/IAllocator_DArena.cpp
Normal file
56
src/alloc2/IAllocator_DArena.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/** @file IAllocator_DArena.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "IAllocator_DArena.hpp"
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
namespace mm {
|
||||
|
||||
std::size_t
|
||||
IAllocator_Impl<DArena>::size(const DArena & s) {
|
||||
return s.limit_ - s.lo_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
IAllocator_Impl<DArena>::committed(const DArena & s) {
|
||||
return s.committed_z_;
|
||||
}
|
||||
|
||||
bool
|
||||
IAllocator_Impl<DArena>::contains(const DArena & s,
|
||||
const void * p)
|
||||
{
|
||||
return (s.lo_ <= p) && (p < s.hi_);
|
||||
}
|
||||
|
||||
std::byte *
|
||||
IAllocator_Impl<DArena>::alloc(const DArena & s,
|
||||
std::size_t z)
|
||||
{
|
||||
(void)s;
|
||||
(void)z;
|
||||
|
||||
// scope log(XO_DEBUG(config_.debug_flag_));
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void
|
||||
IAllocator_Impl<DArena>::clear(DArena & s)
|
||||
{
|
||||
s.free_ = s.lo_;
|
||||
//s.checkpoint_ = s.lo_;
|
||||
}
|
||||
|
||||
void
|
||||
IAllocator_Impl<DArena>::destruct_data(DArena & s)
|
||||
{
|
||||
s.~DArena();
|
||||
}
|
||||
} /*namespace mm*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end IAllocator_DArena.cpp */
|
||||
|
|
@ -4,11 +4,14 @@
|
|||
set(UTEST_EXE utest.alloc2)
|
||||
set(UTEST_SRCS
|
||||
alloc2_utest_main.cpp
|
||||
arena.test.cpp
|
||||
objectmodel.test.cpp)
|
||||
|
||||
if (ENABLE_TESTING)
|
||||
xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS})
|
||||
xo_headeronly_dependency(${UTEST_EXE} xo_facet)
|
||||
xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2)
|
||||
endif()
|
||||
|
||||
|
||||
# end CMakeLists.txt
|
||||
|
|
|
|||
26
utest/arena.test.cpp
Normal file
26
utest/arena.test.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/** @file arena.test.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "xo/alloc2/AAllocator.hpp"
|
||||
#include "xo/alloc2/DArena.hpp"
|
||||
#include "xo/alloc2/IAllocator_DArena.hpp"
|
||||
#include "xo/alloc2/padding.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace xo {
|
||||
using xo::mm::IAllocator_Xfer;
|
||||
using xo::mm::DArena;
|
||||
|
||||
namespace ut {
|
||||
TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]")
|
||||
{
|
||||
IAllocator_Xfer<DArena> xfer;
|
||||
|
||||
REQUIRE(IAllocator_Xfer<DArena>::_valid);
|
||||
}
|
||||
} /*namespace ut*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end arena.test.cpp */
|
||||
|
|
@ -77,12 +77,6 @@
|
|||
* self-sufficent object with convenient interface
|
||||
*
|
||||
* Application code will deal with ubox<AComplex,DPolarCoords>
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
**/
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue