git subrepo clone git@github.com:Rconybea/xo-alloc.git xo-alloc
subrepo: subdir: "xo-alloc" merged: "fc656313" upstream: origin: "git@github.com:Rconybea/xo-alloc.git" branch: "main" commit: "fc656313" git-subrepo: version: "0.4.9" origin: "???" commit: "???"
This commit is contained in:
parent
d16545d815
commit
2c8faf6e43
49 changed files with 7196 additions and 0 deletions
13
xo-alloc/src/alloc/AllocPolicy.cpp
Normal file
13
xo-alloc/src/alloc/AllocPolicy.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* AllocPolicy.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#include "AllocPolicy.hpp"
|
||||
|
||||
/* note: inline/.hpp definition not allowed for operator delete */
|
||||
void operator delete(void * ptr) noexcept {
|
||||
xo::xo.free(ptr);
|
||||
}
|
||||
|
||||
/* end AllocPolicy.cpp */
|
||||
426
xo-alloc/src/alloc/ArenaAlloc.cpp
Normal file
426
xo-alloc/src/alloc/ArenaAlloc.cpp
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
/* file ArenaAlloc.cpp
|
||||
*
|
||||
* author: Roland Conybeare
|
||||
*/
|
||||
|
||||
#include "ArenaAlloc.hpp"
|
||||
#include "Object.hpp"
|
||||
#include "ObjectStatistics.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h> // for getpagesize() on OSX
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
using std::byte;
|
||||
|
||||
namespace gc {
|
||||
namespace {
|
||||
/* alignment better be a power of 2 */
|
||||
std::size_t
|
||||
align_lub(std::size_t x, std::size_t align)
|
||||
{
|
||||
/* e.g:
|
||||
* align = 4096, x%align = 100 -> dx = 3996
|
||||
* align = 4096, x%align = 0 -> dx = 0
|
||||
*/
|
||||
std::size_t dx = (align - (x % align)) % align;
|
||||
|
||||
return x + dx;
|
||||
}
|
||||
}
|
||||
|
||||
ArenaAlloc::ArenaAlloc(const std::string & name,
|
||||
std::size_t z,
|
||||
bool debug_flag)
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag), xtag("name", name));
|
||||
|
||||
constexpr size_t c_hugepage_z = 2 * 1024 * 1024;
|
||||
|
||||
this->name_ = name;
|
||||
this->page_z_ = getpagesize();
|
||||
this->hugepage_z_ = c_hugepage_z;
|
||||
|
||||
// 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 c_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
|
||||
|
||||
z = align_lub(z, c_hugepage_z); // 4.
|
||||
|
||||
// 5.
|
||||
byte * base = reinterpret_cast<byte *>(::mmap(nullptr,
|
||||
z + c_hugepage_z,
|
||||
PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1, 0));
|
||||
|
||||
log && log("acquired memory [lo,hi) using mmap",
|
||||
xtag("lo", base),
|
||||
xtag("z", z),
|
||||
xtag("hi", reinterpret_cast<byte *>(base) + z));
|
||||
|
||||
if (base == MAP_FAILED) {
|
||||
throw std::runtime_error(tostr("ArenaAlloc: uncommitted allocation failed",
|
||||
xtag("size", z)));
|
||||
}
|
||||
|
||||
byte * aligned_base = reinterpret_cast<byte *>(align_lub(reinterpret_cast<size_t>(base),
|
||||
c_hugepage_z));
|
||||
|
||||
assert(reinterpret_cast<size_t>(aligned_base) % c_hugepage_z == 0);
|
||||
assert(aligned_base >= base);
|
||||
assert(aligned_base < base + c_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 + c_hugepage_z;
|
||||
|
||||
if (aligned_hi < hi) {
|
||||
size_t suffix = hi - aligned_hi;
|
||||
|
||||
::munmap(aligned_hi, suffix); // 7.
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
::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_ptr_ = lo_;
|
||||
this->limit_ = lo_;
|
||||
this->hi_ = lo_ + z;
|
||||
this->debug_flag_ = debug_flag;
|
||||
|
||||
if (!lo_) {
|
||||
throw std::runtime_error(tostr("ArenaAlloc: allocation failed",
|
||||
xtag("size", z)));
|
||||
}
|
||||
|
||||
log && log(xtag("lo", (void*)lo_),
|
||||
xtag("page_z", page_z_),
|
||||
xtag("hugepage_z", hugepage_z_));
|
||||
}
|
||||
|
||||
ArenaAlloc::~ArenaAlloc()
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_));
|
||||
|
||||
// hygiene..
|
||||
|
||||
if (lo_) {
|
||||
log && log("unmap [lo,hi)", xtag("lo", lo_), xtag("z", hi_ - lo_), xtag("hi", hi_));
|
||||
|
||||
::munmap(lo_, hi_ - lo_);
|
||||
}
|
||||
// could use this as fallback if we dropped the uncommitted technique
|
||||
//delete [] this->lo_;
|
||||
|
||||
this->lo_ = nullptr;
|
||||
this->committed_z_ = 0;
|
||||
this->checkpoint_ = nullptr;
|
||||
this->free_ptr_ = nullptr;
|
||||
this->limit_ = nullptr;
|
||||
this->hi_ = nullptr;
|
||||
this->debug_flag_ = false;
|
||||
}
|
||||
|
||||
up<ArenaAlloc>
|
||||
ArenaAlloc::make(const std::string & name,
|
||||
std::size_t z, bool debug_flag)
|
||||
{
|
||||
return up<ArenaAlloc>(new ArenaAlloc(name,
|
||||
z, debug_flag));
|
||||
}
|
||||
|
||||
bool
|
||||
ArenaAlloc::expand(size_t offset_z)
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_), xtag("offset_z", offset_z), xtag("committed_z", committed_z_));
|
||||
|
||||
if (offset_z <= committed_z_) {
|
||||
log && log("trivial success, offset within committed range",
|
||||
xtag("offset_z", offset_z),
|
||||
xtag("committed_z", committed_z_));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lo_ + offset_z > hi_) {
|
||||
throw std::runtime_error(tostr("ArenaAlloc::expand: requested size exceeds reserved size",
|
||||
xtag("requested", offset_z), xtag("reserved", reserved())));
|
||||
}
|
||||
|
||||
/*
|
||||
* pre:
|
||||
*
|
||||
* _______________...................................
|
||||
* ^ ^ ^
|
||||
* lo limit hi
|
||||
*
|
||||
* < committed_z >
|
||||
* <----------offset_z----------->
|
||||
* > <- z: 0 <= z < hugepage_z
|
||||
* <---------aligned_offset_z--------->
|
||||
* <--- add_commit_z -->
|
||||
*
|
||||
* post:
|
||||
* ____________________________________..............
|
||||
* ^ ^ ^
|
||||
* lo limit hi
|
||||
*
|
||||
*/
|
||||
|
||||
std::size_t aligned_offset_z = align_lub(offset_z, hugepage_z_);
|
||||
std::byte * commit_start = lo_ + committed_z_;
|
||||
std::size_t add_commit_z = aligned_offset_z - committed_z_;
|
||||
|
||||
assert(limit_ == lo_ + committed_z_);
|
||||
|
||||
log && log(xtag("aligned_offset_z", aligned_offset_z),
|
||||
xtag("add_commit_z", add_commit_z));
|
||||
|
||||
log && log("expand committed range",
|
||||
xtag("commit_start", commit_start),
|
||||
xtag("add_commit_z", add_commit_z),
|
||||
xtag("commit_end", commit_start + add_commit_z));
|
||||
|
||||
if (::mprotect(commit_start, add_commit_z, PROT_READ | PROT_WRITE) != 0) {
|
||||
throw std::runtime_error(tostr("ArenaAlloc::expand: commit failure",
|
||||
xtag("committed_z", committed_z_),
|
||||
xtag("add_commit_z", add_commit_z)));
|
||||
}
|
||||
|
||||
this->committed_z_ = aligned_offset_z;
|
||||
this->limit_ = this->lo_ + committed_z_;
|
||||
|
||||
assert(committed_z_ % hugepage_z_ == 0);
|
||||
assert(reinterpret_cast<size_t>(limit_) % hugepage_z_ == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaAlloc::set_free_ptr(std::byte * x)
|
||||
{
|
||||
assert(lo_ <= x);
|
||||
assert(x < limit_);
|
||||
|
||||
if (lo_ <= x && x < limit_) {
|
||||
this->free_ptr_ = x;
|
||||
if (checkpoint_ > free_ptr_)
|
||||
this->checkpoint_ = free_ptr_;
|
||||
} else {
|
||||
throw std::runtime_error(tostr("LinearAllog::set_free_ptr(x): expected lo <= x < limit",
|
||||
xtag("lo", lo_), xtag("x", x), xtag("limit", limit_)));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, std::size_t>
|
||||
ArenaAlloc::location_of(const void * x) const
|
||||
{
|
||||
if ((lo_ <= x) && (x < hi_)) {
|
||||
return std::make_pair(true, reinterpret_cast<const std::byte *>(x) - lo_);
|
||||
} else {
|
||||
return std::make_pair(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArenaAlloc::reset(std::size_t need_z) {
|
||||
this->clear();
|
||||
this->expand(need_z);
|
||||
}
|
||||
|
||||
void
|
||||
ArenaAlloc::capture_object_statistics(capture_phase phase,
|
||||
ObjectStatistics * p_dest) const
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_),
|
||||
xtag("name", name_),
|
||||
xtag("capacity", limit_ - lo_),
|
||||
xtag("alloc", free_ptr_ - lo_),
|
||||
xtag("lo", (void*)lo_),
|
||||
xtag("free_ptr", (void*)free_ptr_));
|
||||
|
||||
using xo::reflect::TaggedPtr;
|
||||
|
||||
std::byte * p = lo_;
|
||||
|
||||
while (p < free_ptr_) {
|
||||
log && log(xtag("p", (void *)p));
|
||||
|
||||
Object * obj = reinterpret_cast<Object *>(p);
|
||||
TaggedPtr tp = obj->self_tp();
|
||||
std::size_t z = obj->_shallow_size();
|
||||
std::uint32_t id = tp.td()->id().id();
|
||||
|
||||
log && log(xtag("obj", (void*)obj),
|
||||
xtag("z", z),
|
||||
xtag("typeid", id));
|
||||
|
||||
if (p_dest->per_type_stats_v_.size() < id + 1)
|
||||
p_dest->per_type_stats_v_.resize(id + 1);
|
||||
|
||||
PerObjectTypeStatistics & dest = p_dest->per_type_stats_v_.at(id);
|
||||
|
||||
dest.td_ = tp.td();
|
||||
|
||||
log && log(xtag("td", tp.td()->short_name()));
|
||||
|
||||
switch (phase) {
|
||||
case capture_phase::sab:
|
||||
++dest.scanned_n_;
|
||||
dest.scanned_z_ += z;
|
||||
break;
|
||||
case capture_phase::sae:
|
||||
++dest.survive_n_;
|
||||
dest.survive_z_ += z;
|
||||
break;
|
||||
}
|
||||
|
||||
p += z;
|
||||
}
|
||||
|
||||
assert(p == free_ptr_);
|
||||
}
|
||||
|
||||
const std::string &
|
||||
ArenaAlloc::name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::size() const {
|
||||
return limit_ - lo_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::committed() const {
|
||||
return committed_z_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::available() const {
|
||||
return limit_ - free_ptr_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::allocated() const {
|
||||
return free_ptr_ - lo_;
|
||||
}
|
||||
|
||||
bool
|
||||
ArenaAlloc::contains(const void * x) const {
|
||||
return (lo_ <= x) && (x < hi_);
|
||||
}
|
||||
|
||||
bool
|
||||
ArenaAlloc::is_before_checkpoint(const void * x) const {
|
||||
return (lo_ <= x) && (x < checkpoint_);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::before_checkpoint() const
|
||||
{
|
||||
return checkpoint_ - lo_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ArenaAlloc::after_checkpoint() const
|
||||
{
|
||||
return free_ptr_ - checkpoint_;
|
||||
}
|
||||
|
||||
bool
|
||||
ArenaAlloc::check_owned(IObject * src) const
|
||||
{
|
||||
byte * addr = reinterpret_cast<byte *>(src);
|
||||
|
||||
return (lo_ <= addr) && (addr < hi_);
|
||||
}
|
||||
|
||||
bool
|
||||
ArenaAlloc::debug_flag() const
|
||||
{
|
||||
return debug_flag_;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaAlloc::clear()
|
||||
{
|
||||
this->set_free_ptr(lo_);
|
||||
//this->limit_ = hi_;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaAlloc::checkpoint()
|
||||
{
|
||||
this->checkpoint_ = this->free_ptr_;
|
||||
}
|
||||
|
||||
std::byte *
|
||||
ArenaAlloc::alloc(std::size_t z0)
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_));
|
||||
|
||||
/* word size for alignment */
|
||||
constexpr uint32_t c_bpw = sizeof(std::uintptr_t);
|
||||
(void)c_bpw;
|
||||
|
||||
std::uintptr_t free_u64 = reinterpret_cast<std::uintptr_t>(free_ptr_);
|
||||
(void)free_u64;
|
||||
|
||||
assert(free_u64 % c_bpw == 0ul);
|
||||
|
||||
std::uint32_t dz = alloc_padding(z0);
|
||||
|
||||
std::size_t z1 = z0 + dz;
|
||||
|
||||
assert(z1 % c_bpw == 0ul);
|
||||
|
||||
this->expand(this->allocated() + z1);
|
||||
|
||||
std::byte * retval = this->free_ptr_;
|
||||
|
||||
log && log(xtag("self", name_),
|
||||
xtag("z0", z0),
|
||||
xtag("+pad", dz),
|
||||
xtag("z1", z1),
|
||||
xtag("size", this->size()),
|
||||
xtag("avail", this->available()));
|
||||
|
||||
this->free_ptr_ += z1;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end ArenaAlloc.cpp */
|
||||
57
xo-alloc/src/alloc/Blob.cpp
Normal file
57
xo-alloc/src/alloc/Blob.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/** @file Blob.cpp
|
||||
*
|
||||
* @author Roland Conybeare, Nov 2025
|
||||
**/
|
||||
|
||||
#include "Blob.hpp"
|
||||
#include "xo/reflect/Reflect.hpp"
|
||||
#include "xo/allocutil/IAlloc.hpp"
|
||||
|
||||
namespace xo {
|
||||
using xo::reflect::Reflect;
|
||||
using xo::reflect::TaggedPtr;
|
||||
|
||||
gp<Blob>
|
||||
Blob::make(gc::IAlloc * mm, std::size_t z) {
|
||||
std::byte * mem = mm->alloc(sizeof(Blob) + z);
|
||||
|
||||
return new (mem) Blob(z);
|
||||
}
|
||||
|
||||
TaggedPtr
|
||||
Blob::self_tp() const
|
||||
{
|
||||
return Reflect::make_tp(const_cast<Blob*>(this));
|
||||
}
|
||||
|
||||
void
|
||||
Blob::display(std::ostream & os) const
|
||||
{
|
||||
os << "<blob" << xtag("z", z_) << ">";
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Blob::_shallow_size() const {
|
||||
return sizeof(Blob) + z_;
|
||||
}
|
||||
|
||||
Object *
|
||||
Blob::_shallow_copy(gc::IAlloc * mm) const {
|
||||
Cpof cpof(mm, this);
|
||||
std::byte * cp_mem = mm->alloc_gc_copy(sizeof(Blob) + z_, this);
|
||||
|
||||
gp<Blob> copy = new (cp_mem) Blob(z_);
|
||||
|
||||
::memcpy(copy->data(), data_, z_);
|
||||
|
||||
return copy.get();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Blob::_forward_children(gc::IAlloc *)
|
||||
{
|
||||
return this->_shallow_size();
|
||||
}
|
||||
}
|
||||
|
||||
/* end Blob.cpp */
|
||||
24
xo-alloc/src/alloc/CMakeLists.txt
Normal file
24
xo-alloc/src/alloc/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# alloc/CMakeLists.txt
|
||||
|
||||
set(SELF_LIB xo_alloc)
|
||||
set(SELF_SRCS
|
||||
ArenaAlloc.cpp
|
||||
ListAlloc.cpp
|
||||
GC.cpp
|
||||
GcStatistics.cpp
|
||||
ObjectStatistics.cpp
|
||||
Object.cpp
|
||||
Blob.cpp
|
||||
Forwarding1.cpp
|
||||
generation.cpp
|
||||
)
|
||||
|
||||
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
|
||||
xo_headeronly_dependency(${SELF_LIB} xo_allocutil)
|
||||
# xo-unit used for time measurement
|
||||
xo_headeronly_dependency(${SELF_LIB} xo_unit)
|
||||
xo_dependency(${SELF_LIB} indentlog)
|
||||
xo_dependency(${SELF_LIB} reflect)
|
||||
xo_headeronly_dependency(${SELF_LIB} callback)
|
||||
|
||||
#end CMakeLists.txt
|
||||
79
xo-alloc/src/alloc/Forwarding1.cpp
Normal file
79
xo-alloc/src/alloc/Forwarding1.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/* file Forwarding1.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2025
|
||||
*/
|
||||
|
||||
#include "Forwarding1.hpp"
|
||||
#include "xo/reflect/Reflect.hpp"
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
using xo::reflect::Reflect;
|
||||
using xo::reflect::TaggedPtr;
|
||||
|
||||
namespace obj {
|
||||
Forwarding1::Forwarding1(gp<IObject> dest)
|
||||
: dest_{dest}
|
||||
{}
|
||||
|
||||
TaggedPtr
|
||||
Forwarding1::self_tp() const
|
||||
{
|
||||
return Reflect::make_tp(const_cast<Forwarding1*>(this));
|
||||
}
|
||||
|
||||
void
|
||||
Forwarding1::display(std::ostream & os) const
|
||||
{
|
||||
os << "<fwd"
|
||||
<< xtag("dest", (void*)dest_.ptr())
|
||||
// << xtag("dest-td", dest_->self_tp().td()->short_name())
|
||||
<< ">";
|
||||
}
|
||||
|
||||
IObject *
|
||||
Forwarding1::_offset_destination(IObject * src) const
|
||||
{
|
||||
intptr_t offset = src - static_cast<const IObject *>(this);
|
||||
|
||||
return dest_.ptr() + offset;
|
||||
}
|
||||
|
||||
IObject *
|
||||
Forwarding1::_destination() {
|
||||
return dest_.ptr();
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
std::size_t
|
||||
Forwarding1::_shallow_size() const {
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// LCOV_EXCL_START
|
||||
IObject *
|
||||
Forwarding1::_shallow_copy(gc::IAlloc *) const {
|
||||
/* forwarding objects are never copied */
|
||||
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// LCOV_EXCL_START
|
||||
std::size_t
|
||||
Forwarding1::_forward_children(gc::IAlloc *) {
|
||||
/* forwarding objects are never traced */
|
||||
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
} /*namespace obj*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Forwarding1.cpp */
|
||||
1526
xo-alloc/src/alloc/GC.cpp
Normal file
1526
xo-alloc/src/alloc/GC.cpp
Normal file
File diff suppressed because it is too large
Load diff
214
xo-alloc/src/alloc/GcStatistics.cpp
Normal file
214
xo-alloc/src/alloc/GcStatistics.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/* GcStatistics.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2025
|
||||
*/
|
||||
|
||||
#include "GcStatistics.hpp"
|
||||
#include "xo/indentlog/print/pretty_vector.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
void
|
||||
PerGenerationStatistics::include_gc(std::size_t alloc_z,
|
||||
std::size_t before_z,
|
||||
std::size_t after_z,
|
||||
std::size_t promote_z)
|
||||
{
|
||||
this->update_snapshot(after_z);
|
||||
|
||||
//++n_gc_;
|
||||
new_alloc_z_ += alloc_z;
|
||||
scanned_z_ += before_z;
|
||||
survive_z_ += after_z;
|
||||
promote_z_ += promote_z;
|
||||
}
|
||||
|
||||
void
|
||||
PerGenerationStatistics::update_snapshot(std::size_t after_z)
|
||||
{
|
||||
used_z_ = after_z;
|
||||
}
|
||||
|
||||
void
|
||||
PerGenerationStatistics::display(std::ostream & os) const
|
||||
{
|
||||
os << "<PerGenerationStatistics"
|
||||
<< xrtag("used", used_z_)
|
||||
<< xrtag("n_gc", n_gc_)
|
||||
<< xrtag("new_alloc_z", new_alloc_z_)
|
||||
<< xrtag("scanned_z", scanned_z_)
|
||||
<< xrtag("survive_z", survive_z_)
|
||||
<< xrtag("promote_z", promote_z_)
|
||||
<< ">";
|
||||
}
|
||||
|
||||
void
|
||||
GcStatistics::begin_gc(generation upto,
|
||||
std::size_t new_alloc)
|
||||
{
|
||||
++(this->gen_v_[static_cast<std::size_t>(upto)].n_gc_);
|
||||
this->total_allocated_ += new_alloc;
|
||||
this->total_promoted_sab_ = total_promoted_;
|
||||
}
|
||||
|
||||
void
|
||||
GcStatistics::include_gc(generation upto,
|
||||
std::size_t alloc_z,
|
||||
std::size_t before_z,
|
||||
std::size_t after_z,
|
||||
std::size_t promote_z)
|
||||
{
|
||||
gen_v_[static_cast<std::size_t>(upto)].include_gc(alloc_z, before_z, after_z, promote_z);
|
||||
}
|
||||
|
||||
void
|
||||
GcStatistics::update_snapshot(generation upto,
|
||||
std::size_t after_z)
|
||||
{
|
||||
gen_v_[static_cast<std::size_t>(upto)].update_snapshot(after_z);
|
||||
}
|
||||
|
||||
void
|
||||
GcStatistics::display(std::ostream & os) const
|
||||
{
|
||||
os << "<GcStatistics"
|
||||
<< xrtag("gen_v", gen_v_)
|
||||
<< xrtag("total_allocated", total_allocated_)
|
||||
<< xrtag("total_promoted_sab", total_promoted_sab_)
|
||||
// total_promoted
|
||||
// n_mtuation
|
||||
// n_logged_mutation
|
||||
// n_xgen_mutation
|
||||
// n_xckp_mutation
|
||||
// << xtag("per_type_stats", per_type_stats_)
|
||||
<< ">";
|
||||
}
|
||||
|
||||
void
|
||||
GcStatisticsExt::display(std::ostream & os) const
|
||||
{
|
||||
os << "<GcStatisticsExt"
|
||||
<< xrtag("gen_v", gen_v_)
|
||||
<< xrtag("total_allocated", total_allocated_)
|
||||
<< xrtag("total_promoted_sab", total_promoted_)
|
||||
<< xrtag("nursery_z", nursery_z_)
|
||||
<< xrtag("nursery_before_ckp_z", nursery_before_checkpoint_z_)
|
||||
<< xrtag("nursery_after_ckp_z", nursery_after_checkpoint_z_)
|
||||
<< xrtag("tenured_z", tenured_z_)
|
||||
<< xrtag("n_mutation", n_mutation_)
|
||||
<< xrtag("n_logged_mutation", n_logged_mutation_)
|
||||
<< xrtag("n_xgen_mutation", n_xgen_mutation_)
|
||||
<< xrtag("n_xckp_mutation", n_xckp_mutation_)
|
||||
// << xtag("per_type_stats", per_type_stats_)
|
||||
<< ">";
|
||||
}
|
||||
|
||||
float
|
||||
GcStatisticsHistoryItem::collection_rate() const {
|
||||
using namespace xo::qty::qty;
|
||||
|
||||
float gz = this->garbage_z();
|
||||
|
||||
auto dt_nanos = this->dt_.with_repr<float>();
|
||||
auto dt_sec = dt_nanos.rescale_ext<xo::qty::u::second>();
|
||||
auto rate = gz / dt_sec;
|
||||
float retval = rate.scale();
|
||||
|
||||
//scope log(XO_DEBUG(true));
|
||||
//log && log(xtag("gz", gz), xtag("dt_sec", dt_sec), xtag("rate", rate), xtag("rate/sec", retval));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
GcStatisticsHistoryItem::display(std::ostream & os) const
|
||||
{
|
||||
os << "<GcStatisticsHistoryItem"
|
||||
<< xrtag("upto", upto_)
|
||||
<< xrtag("survive_z", survive_z_)
|
||||
<< xrtag("promote_z", promote_z_)
|
||||
<< xrtag("persist_z", persist_z_)
|
||||
<< xrtag("effort_z", effort_z_)
|
||||
<< xrtag("garbage0_z", garbage0_z_)
|
||||
<< xrtag("garbage1_z", garbage1_z_)
|
||||
<< xrtag("garbageN_z", garbageN_z_)
|
||||
<< xrtag("dt", dt_)
|
||||
<< ">";
|
||||
}
|
||||
|
||||
} /*namespace gc*/
|
||||
|
||||
namespace print {
|
||||
bool
|
||||
ppdetail<xo::gc::PerGenerationStatistics>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::PerGenerationStatistics & x)
|
||||
{
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"PerGenerationStatistics",
|
||||
refrtag("used_z", x.used_z_),
|
||||
refrtag("n_gc", x.n_gc_),
|
||||
refrtag("new_alloc_z", x.new_alloc_z_),
|
||||
refrtag("scanned_z", x.scanned_z_),
|
||||
refrtag("survive_z", x.survive_z_),
|
||||
refrtag("promote_z", x.promote_z_)
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
ppdetail<xo::gc::GcStatistics>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::GcStatistics & x)
|
||||
{
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"GcStatistics",
|
||||
refrtag("gen_v", x.gen_v_),
|
||||
refrtag("total_allocated", x.total_allocated_),
|
||||
refrtag("total_promoted_sab", x.total_promoted_sab_),
|
||||
refrtag("total_promoted", x.total_promoted_),
|
||||
refrtag("n_mutation", x.n_mutation_),
|
||||
refrtag("n_logged_mutation", x.n_logged_mutation_),
|
||||
refrtag("n_xgen_mutation", x.n_xgen_mutation_),
|
||||
refrtag("n_xckp_mutation", x.n_xckp_mutation_)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ppdetail<xo::gc::GcStatisticsExt>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::GcStatisticsExt & x)
|
||||
{
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"GcStatisticsExt",
|
||||
refrtag("gen_v", x.gen_v_),
|
||||
refrtag("total_allocated", x.total_allocated_),
|
||||
refrtag("total_promoted_sab", x.total_promoted_sab_),
|
||||
refrtag("total_promoted", x.total_promoted_),
|
||||
refrtag("n_mutation", x.n_mutation_),
|
||||
refrtag("n_logged_mutation", x.n_logged_mutation_),
|
||||
refrtag("n_xgen_mutation", x.n_xgen_mutation_),
|
||||
refrtag("n_xckp_mutation", x.n_xckp_mutation_),
|
||||
refrtag("nursery_z", x.nursery_z_),
|
||||
refrtag("nursery_before_checkpoint_z", x.nursery_before_checkpoint_z_),
|
||||
refrtag("nursery_after_checkpoint_z", x.nursery_after_checkpoint_z_),
|
||||
refrtag("tenured_z", x.tenured_z_));
|
||||
}
|
||||
|
||||
bool
|
||||
ppdetail<xo::gc::GcStatisticsHistoryItem>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::GcStatisticsHistoryItem & x)
|
||||
{
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"GcStatisticsHistoryItem",
|
||||
refrtag("upto", gen2str(x.upto_)),
|
||||
refrtag("survive_z", x.survive_z_),
|
||||
refrtag("promote_z", x.promote_z_),
|
||||
refrtag("persist_z", x.persist_z_),
|
||||
refrtag("effort_z", x.effort_z_),
|
||||
refrtag("garbage0_z", x.garbage0_z_),
|
||||
refrtag("garbage1_z", x.garbage1_z_),
|
||||
refrtag("garbageN_z", x.garbageN_z_),
|
||||
refrtag("dt", x.dt_));
|
||||
}
|
||||
} /*namespace print*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end GcStatistics.cpp */
|
||||
400
xo-alloc/src/alloc/ListAlloc.cpp
Normal file
400
xo-alloc/src/alloc/ListAlloc.cpp
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
/* file ListAlloc.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#include "ListAlloc.hpp"
|
||||
#include "ArenaAlloc.hpp"
|
||||
#include "xo/indentlog/scope.hpp"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
ListAlloc::ListAlloc(std::unique_ptr<ArenaAlloc> hd,
|
||||
ArenaAlloc * marked,
|
||||
std::size_t cz, std::size_t nz, std::size_t tz,
|
||||
bool debug_flag)
|
||||
: start_z_{cz},
|
||||
hd_{std::move(hd)},
|
||||
marked_{marked},
|
||||
full_l_{},
|
||||
current_z_{cz},
|
||||
next_z_{nz},
|
||||
total_z_{tz},
|
||||
debug_flag_{debug_flag}
|
||||
{}
|
||||
|
||||
ListAlloc::~ListAlloc()
|
||||
{
|
||||
this->clear();
|
||||
}
|
||||
|
||||
up<ListAlloc>
|
||||
ListAlloc::make(const std::string & name, std::size_t cz, std::size_t nz, bool debug_flag)
|
||||
{
|
||||
std::unique_ptr<ArenaAlloc> hd{ArenaAlloc::make(name,
|
||||
cz, debug_flag)};
|
||||
|
||||
if (!hd)
|
||||
return nullptr;
|
||||
|
||||
ArenaAlloc * marked = nullptr;
|
||||
|
||||
up<ListAlloc> retval{new ListAlloc(std::move(hd),
|
||||
marked,
|
||||
cz, nz, cz,
|
||||
debug_flag)};
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
ListAlloc::capture_object_statistics(capture_phase phase,
|
||||
ObjectStatistics * p_dest) const
|
||||
{
|
||||
hd_->capture_object_statistics(phase, p_dest);
|
||||
|
||||
for (const auto & arena : full_l_)
|
||||
arena->capture_object_statistics(phase, p_dest);
|
||||
|
||||
}
|
||||
|
||||
const std::string &
|
||||
ListAlloc::name() const {
|
||||
if (hd_) {
|
||||
return hd_->name();
|
||||
}
|
||||
|
||||
static std::string s_default_name = "ListAlloc";
|
||||
return s_default_name;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::page_size() const {
|
||||
return hd_->page_size();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::hugepage_z() const {
|
||||
return hd_->hugepage_z();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::size() const {
|
||||
return total_z_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::committed() const {
|
||||
std::size_t z = 0;
|
||||
if (hd_)
|
||||
z += hd_->committed();
|
||||
for (const auto & a : full_l_)
|
||||
z += a->committed();
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
std::byte *
|
||||
ListAlloc::free_ptr() const {
|
||||
return hd_->free_ptr();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::available() const {
|
||||
if (hd_) {
|
||||
/* can only allocate from @ref hd_,
|
||||
* so even if there were available space in @ref full_l_,
|
||||
* it's not accessible to ListAlloc.
|
||||
*/
|
||||
|
||||
return hd_->available();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::allocated() const {
|
||||
std::size_t total = 0;
|
||||
|
||||
if (hd_) {
|
||||
total += hd_->allocated();
|
||||
}
|
||||
|
||||
for (const auto & alloc : full_l_)
|
||||
total += alloc->allocated();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
bool
|
||||
ListAlloc::contains(const void * x) const {
|
||||
if (hd_ && hd_->contains(x))
|
||||
return true;
|
||||
|
||||
for (const auto & alloc : full_l_) {
|
||||
if (alloc->contains(x))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ListAlloc::is_before_checkpoint(const void * x) const {
|
||||
if (!marked_)
|
||||
return true;
|
||||
|
||||
if (marked_ && marked_->contains(x))
|
||||
return marked_->is_before_checkpoint(x);
|
||||
|
||||
/*
|
||||
* 1. allocs in full_l_ appear in oldest-to-youngest order
|
||||
* 2. allocators that appear before marked_ in full_l_ count as 'before checkpoint'
|
||||
* 3. allocators that appear after marked_ in full_l_ count as 'after checkpoint'
|
||||
*/
|
||||
|
||||
bool older_than_marked = true;
|
||||
|
||||
for (const auto & alloc : full_l_) {
|
||||
if (older_than_marked) {
|
||||
if (alloc.get() == marked_) {
|
||||
/* nothing else to test on this iteration,
|
||||
* already checked .marked_ specifically
|
||||
*/
|
||||
break;
|
||||
} else {
|
||||
/* before checkpoint */
|
||||
if (alloc->contains(x))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::before_checkpoint() const
|
||||
{
|
||||
scope log(XO_DEBUG(false && debug_flag_), xtag("marked", marked_ ? marked_->name() : ""));
|
||||
|
||||
if (marked_) {
|
||||
if (full_l_.empty()) {
|
||||
assert(marked_ == hd_.get());
|
||||
|
||||
return marked_->before_checkpoint();
|
||||
} else {
|
||||
std::size_t z = 0;
|
||||
|
||||
/* control here: .marked & .full_l non-empty. */
|
||||
if (hd_.get() == marked_) {
|
||||
z += hd_->before_checkpoint();
|
||||
|
||||
/* anything in .full_l is older than marked .hd */
|
||||
for (const auto & alloc : full_l_) {
|
||||
z += alloc->allocated();
|
||||
}
|
||||
|
||||
return z;
|
||||
} else {
|
||||
/* messiest case: .marked is true,
|
||||
* and not the youngest arena
|
||||
*/
|
||||
|
||||
/* full_l always in increasing time order: oldest-to-youngest order */
|
||||
size_t i_alloc = 0;
|
||||
for (const auto & alloc : full_l_) {
|
||||
log && log(xtag("i_alloc", i_alloc),
|
||||
xtag("alloc", alloc->name()),
|
||||
xtag("z", z));
|
||||
|
||||
if (alloc.get() == marked_) {
|
||||
log && log("marked", xtag("+z", marked_->before_checkpoint()));
|
||||
z += marked_->before_checkpoint();
|
||||
break;
|
||||
} else {
|
||||
log && log("older than marked", xtag("+z", alloc->allocated()));
|
||||
z += alloc->allocated();
|
||||
}
|
||||
++i_alloc;
|
||||
}
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
} else {
|
||||
/* count *everything* allocated */
|
||||
return this->allocated();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ListAlloc::after_checkpoint() const
|
||||
{
|
||||
scope log(XO_DEBUG(false && debug_flag_), xtag("marked", marked_ ? marked_->name() : ""));
|
||||
|
||||
if (!marked_)
|
||||
return 0;
|
||||
|
||||
if (full_l_.empty()) {
|
||||
assert(marked_ == hd_.get());
|
||||
|
||||
return marked_->after_checkpoint();
|
||||
}
|
||||
|
||||
bool older_than_marked = true;
|
||||
|
||||
std::size_t z = 0;
|
||||
|
||||
std::size_t i_alloc = 0;
|
||||
for (const auto & alloc : full_l_) {
|
||||
log && log(xtag("i_alloc", i_alloc),
|
||||
xtag("alloc", alloc->name()),
|
||||
xtag("z", z));
|
||||
|
||||
if (older_than_marked) {
|
||||
if (alloc.get() == marked_) {
|
||||
log && log("marked", xtag("+z", marked_->after_checkpoint()));
|
||||
older_than_marked = false;
|
||||
z += marked_->after_checkpoint();
|
||||
}
|
||||
} else {
|
||||
/* younger than marked */
|
||||
log && log("younger", xtag("+z", alloc->allocated()));
|
||||
z += alloc->allocated();
|
||||
}
|
||||
|
||||
++i_alloc;
|
||||
}
|
||||
|
||||
/** head must be included, since it's always the youngest bucket **/
|
||||
z += hd_->after_checkpoint();
|
||||
|
||||
log && log("z", z);
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
bool
|
||||
ListAlloc::debug_flag() const {
|
||||
return debug_flag_;
|
||||
}
|
||||
|
||||
void
|
||||
ListAlloc::clear() {
|
||||
// general hygiene
|
||||
start_z_ = 0;
|
||||
hd_.reset();
|
||||
marked_ = nullptr;
|
||||
full_l_.clear();
|
||||
current_z_ = 0;
|
||||
next_z_ = 0;
|
||||
total_z_ = 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ListAlloc::reset(std::size_t z)
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_), xtag("z", z));
|
||||
|
||||
bool recycle_head_bucket = hd_ && (z <= hd_->size());
|
||||
|
||||
this->full_l_.clear();
|
||||
this->marked_ = nullptr;
|
||||
|
||||
if (recycle_head_bucket) {
|
||||
this->hd_->clear();
|
||||
this->total_z_ = hd_->size();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
std::string old_name = this->hd_->name();
|
||||
|
||||
this->hd_.reset(nullptr);
|
||||
this->total_z_ = 0;
|
||||
|
||||
return this->expand(z, old_name + "+");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ListAlloc::expand(std::size_t z, const std::string & name)
|
||||
{
|
||||
scope log(XO_DEBUG(debug_flag_), xtag("name", name));
|
||||
|
||||
//log && log("before", xtag("before_ckp", this->before_checkpoint()));
|
||||
|
||||
std::size_t cz = current_z_;
|
||||
std::size_t nz = next_z_;
|
||||
std::size_t tz;
|
||||
|
||||
do {
|
||||
tz = cz + nz;
|
||||
cz = nz;
|
||||
nz = tz;
|
||||
} while (cz < z);
|
||||
|
||||
log && log("expand to", xtag("cz", cz));
|
||||
|
||||
std::unique_ptr<ArenaAlloc> new_alloc = ArenaAlloc::make(name,
|
||||
cz, debug_flag_);
|
||||
cz = new_alloc->size();
|
||||
|
||||
if (!new_alloc)
|
||||
return false;
|
||||
|
||||
this->current_z_ = cz;
|
||||
this->next_z_ = nz;
|
||||
this->total_z_ += cz;
|
||||
|
||||
if (hd_)
|
||||
this->full_l_.push_back(std::move(hd_));
|
||||
|
||||
this->hd_ = std::move(new_alloc);
|
||||
|
||||
//log && log("after", xtag("before_ckp", this->before_checkpoint()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ListAlloc::checkpoint() {
|
||||
scope log(XO_DEBUG(debug_flag_));
|
||||
|
||||
hd_->checkpoint();
|
||||
|
||||
this->marked_ = hd_.get();
|
||||
|
||||
log && log(xtag("hd", (void*)hd_.get()), xtag("marked", (void*)marked_));
|
||||
}
|
||||
|
||||
std::byte *
|
||||
ListAlloc::alloc(std::size_t z) {
|
||||
scope log(XO_DEBUG(debug_flag_));
|
||||
|
||||
/* ArenaAlloc::alloc() may modify its own size */
|
||||
|
||||
std::size_t z_pre = hd_->size();
|
||||
std::byte * retval = hd_->alloc(z);
|
||||
|
||||
if (retval) {
|
||||
std::size_t z_post = hd_->size();
|
||||
this->total_z_ += (z_post - z_pre);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
log && log("space exhausted -> expand");
|
||||
|
||||
if (this->expand(z, hd_->name() + "+"))
|
||||
return hd_->alloc(z);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end ListAlloc.cpp */
|
||||
230
xo-alloc/src/alloc/Object.cpp
Normal file
230
xo-alloc/src/alloc/Object.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/* Object.cpp
|
||||
*
|
||||
* author: Roalnd Conybeare, Jul 2025
|
||||
*/
|
||||
|
||||
#include "Object.hpp"
|
||||
#include "GC.hpp"
|
||||
#include "Forwarding1.hpp"
|
||||
|
||||
using xo::obj::Forwarding1;
|
||||
|
||||
void *
|
||||
operator new (std::size_t z, const xo::Cpof & cpof)
|
||||
{
|
||||
using xo::gc::GC;
|
||||
|
||||
//GC * gc = reinterpret_cast<GC *>(cpof.mm_);
|
||||
|
||||
return cpof.mm_->alloc_gc_copy(z, cpof.src_);
|
||||
}
|
||||
|
||||
namespace xo {
|
||||
using xo::reflect::TaggedPtr;
|
||||
|
||||
gc::IAlloc *
|
||||
Object::mm = nullptr;
|
||||
|
||||
TaggedPtr
|
||||
Object::self_tp() const
|
||||
{
|
||||
assert(false);
|
||||
return TaggedPtr::universal_null();
|
||||
}
|
||||
|
||||
void
|
||||
Object::display(std::ostream & os) const
|
||||
{
|
||||
os << "<Object>";
|
||||
}
|
||||
|
||||
IObject *
|
||||
Object::_forward(IObject * src,
|
||||
gc::IAlloc * gc)
|
||||
{
|
||||
scope log(XO_DEBUG(gc->debug_flag()), xtag("src", src));
|
||||
|
||||
if (!src)
|
||||
return src;
|
||||
|
||||
if (src->_is_forwarded()) {
|
||||
log && log("already forwarded", xtag("dest", src->_offset_destination(src)));
|
||||
return src->_offset_destination(src);
|
||||
}
|
||||
|
||||
if (gc->check_move(src)) {
|
||||
log && log("needs forwarding");
|
||||
Object::_shallow_move(src, gc);
|
||||
|
||||
/* *src is now a forwarding pointer to a copy in to-space */
|
||||
return src->_offset_destination(src);
|
||||
} else {
|
||||
log && log("already tenured + incr collection");
|
||||
/* don't move tenured objects during incremental collection */
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
IObject *
|
||||
Object::_deep_move(IObject * from_src, gc::GC * gc, gc::ObjectStatistics * /*stats*/)
|
||||
{
|
||||
scope log(XO_DEBUG(gc->config().debug_flag_));
|
||||
|
||||
using gc::generation;
|
||||
|
||||
if (!from_src)
|
||||
return nullptr;
|
||||
|
||||
IObject * retval = from_src->_destination();
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!gc->check_move(from_src)) {
|
||||
/** incremental collection does not move already-tenured objects **/
|
||||
return from_src;
|
||||
}
|
||||
|
||||
/**
|
||||
* To-space:
|
||||
*
|
||||
* to_lo = start of to-space
|
||||
* w,W = white objects. An object x is white if x
|
||||
* + all immediate children of x are in to-space
|
||||
* (also implies this GC cycle put it there)
|
||||
* g,G = grey objects. An object x is gray if it's in to-space,
|
||||
* but possibly has >0 black children
|
||||
* _ = free to-space memory
|
||||
* N = nursery space
|
||||
* T = tenured space
|
||||
*
|
||||
* wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________...
|
||||
* ^ ^ ^
|
||||
* to_lo grey_lo(N) free_ptr(N)
|
||||
*
|
||||
* After moving children of one object, advancing {nursery_grey_lo, nursery_free_ptr}
|
||||
*
|
||||
* wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG______...
|
||||
* ^ ^ ^
|
||||
* to_lo grey_lo(N) free_ptr(N)
|
||||
*
|
||||
* Invariant:
|
||||
*
|
||||
* objects in [to_lo, gray_lo) are white.
|
||||
* all gray objects are in [gray_lo, free_ptr)
|
||||
* memory starting at free_ptr is free.
|
||||
*
|
||||
* deep_move terminates when gray_lo catches up to free_ptr
|
||||
*
|
||||
* Above is simplified. Complication is that GC (including incremental) may
|
||||
* promote objects from nursery (N) to tenured (T)
|
||||
*
|
||||
* So more accurate before/after picture
|
||||
*
|
||||
* N wwwwwwwwwwwwwwwwwwwggggggggggggggggggggg_________________...
|
||||
* ^ ^ ^
|
||||
* to_lo(N) grey_lo(N) free_ptr(N)
|
||||
*
|
||||
* T wwwwwwwwwwwwwwgggggggggggg_______________________________...
|
||||
* ^ ^ ^
|
||||
* to_lo(T) grey_lo(T) free_ptr(N)
|
||||
*
|
||||
* After moving children of one object, advancing {nursery_grey_lo, nursery_free_ptr}
|
||||
*
|
||||
* N wwwwwwwwwwwwwwwwwwwWWWWgggggggggggggggggGGGGGGGGGGG_____...
|
||||
* ^ ^ ^
|
||||
* to_lo(N) grey_lo(N) free_ptr(N)
|
||||
*
|
||||
* T wwwwwwwwwwwwwwggggggggggggGGGGG_________________________...
|
||||
* ^ ^ ^
|
||||
* to_lo(T) grey_lo(T) free_ptr(T)
|
||||
*
|
||||
* deep_move terminates when both:
|
||||
* - gray_lo(N) catches up with free_ptr(N)
|
||||
* - gray_lo(T) catches up with free_ptr(T)
|
||||
*
|
||||
**/
|
||||
|
||||
std::array<std::byte *, gen2int(generation::N)> gray_lo_v
|
||||
= { gc->free_ptr(generation::nursery), gc->free_ptr(generation::tenured) };
|
||||
|
||||
IObject * to_src = Object::_shallow_move(from_src, gc);
|
||||
|
||||
std::size_t fixup_work = 0;
|
||||
do {
|
||||
fixup_work = 0;
|
||||
|
||||
auto fixup_generation = [gc, &log, &gray_lo_v](generation gen) {
|
||||
std::size_t work = 0;
|
||||
while(gray_lo_v[gen2int(gen)] < gc->free_ptr(gen)) {
|
||||
Object * x = reinterpret_cast<Object *>(gray_lo_v[gen2int(gen)]);
|
||||
|
||||
// update per-class stats here
|
||||
|
||||
log && log("fwd children", xtag("x", x));
|
||||
|
||||
std::size_t xz = x->_forward_children(gc);
|
||||
|
||||
// must pad xz to multiple of word size,
|
||||
// to match behavior of LinearAlloc::alloc()
|
||||
//
|
||||
xz += gc::IAlloc::alloc_padding(xz);
|
||||
|
||||
gray_lo_v[gen2int(gen)] += xz;
|
||||
++work;
|
||||
}
|
||||
|
||||
return work;
|
||||
};
|
||||
|
||||
fixup_work += fixup_generation(generation::nursery);
|
||||
fixup_work += fixup_generation(generation::tenured);
|
||||
} while (fixup_work > 0);
|
||||
|
||||
return to_src;
|
||||
} /*deep_move*/
|
||||
|
||||
IObject *
|
||||
Object::_shallow_move(IObject * src, gc::IAlloc * gc)
|
||||
{
|
||||
/* filter for source objects that are owned by GC.
|
||||
* Care required though -- during GC from/to spaces have been swapped already
|
||||
*/
|
||||
if (gc->check_owned(src))
|
||||
{
|
||||
IObject * dest = src->_shallow_copy(gc);
|
||||
|
||||
if (dest != src)
|
||||
src->_forward_to(dest);
|
||||
|
||||
return dest;
|
||||
} else {
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Object::_forward_to(IObject * dest)
|
||||
{
|
||||
char * mem = reinterpret_cast<char *>(this);
|
||||
|
||||
Forwarding1 * fwd = new (mem) Forwarding1(dest);
|
||||
|
||||
(void)fwd;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
operator<< (std::ostream & os, gp<Object> x)
|
||||
{
|
||||
if (x.ptr()) {
|
||||
x->display(os);
|
||||
} else {
|
||||
os << "<nullptr>";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Object.cpp*/
|
||||
73
xo-alloc/src/alloc/ObjectStatistics.cpp
Normal file
73
xo-alloc/src/alloc/ObjectStatistics.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* file ObjectStatistics.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2025
|
||||
*/
|
||||
|
||||
#include "ObjectStatistics.hpp"
|
||||
#include "xo/reflect/TypeDescr.hpp"
|
||||
#include "xo/indentlog/print/pretty_vector.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
void
|
||||
PerObjectTypeStatistics::display(std::ostream & os) const
|
||||
{
|
||||
os << "<PerObjectTypeStatistics";
|
||||
if (td_)
|
||||
os << xrtag("td", td_->short_name());
|
||||
else
|
||||
os << xrtag("td", "nullptr");
|
||||
os << xrtag("scanned_n", scanned_n_)
|
||||
<< xrtag("scanned_z", scanned_z_)
|
||||
<< xrtag("survive_n", survive_n_)
|
||||
<< xrtag("survive_z", survive_z_)
|
||||
<< ">";
|
||||
}
|
||||
|
||||
void
|
||||
ObjectStatistics::display(std::ostream & os) const
|
||||
{
|
||||
os << "<ObjectStatistics";
|
||||
|
||||
std::size_t i = 0;
|
||||
for (const auto & x : per_type_stats_v_) {
|
||||
os << " :[" << i << "] " << x;
|
||||
}
|
||||
|
||||
os << ">";
|
||||
}
|
||||
} /*namespace gc*/
|
||||
|
||||
namespace print {
|
||||
bool
|
||||
ppdetail<xo::gc::PerObjectTypeStatistics>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::PerObjectTypeStatistics & x)
|
||||
{
|
||||
static constexpr std::string_view c_nullptr_str = "nullptr";
|
||||
|
||||
if (x.td_) {
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"PerObjectTypeStatistics",
|
||||
refrtag("td", x.td_ ? x.td_->short_name() : c_nullptr_str),
|
||||
refrtag("scanned_n", x.scanned_n_),
|
||||
refrtag("scanned_z", x.scanned_z_),
|
||||
refrtag("survive_n", x.survive_n_),
|
||||
refrtag("survive_z", x.survive_z_));
|
||||
} else {
|
||||
/* print nothing -- empty struct */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ppdetail<xo::gc::ObjectStatistics>::print_pretty(const ppindentinfo & ppii,
|
||||
const xo::gc::ObjectStatistics & x)
|
||||
{
|
||||
return ppii.pps()->pretty_struct(ppii,
|
||||
"ObjectTypeStatistics",
|
||||
refrtag("per_type_stats_v", x.per_type_stats_v_));
|
||||
}
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end ObjectStatistics.cpp */
|
||||
31
xo-alloc/src/alloc/generation.cpp
Normal file
31
xo-alloc/src/alloc/generation.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* generation.cpp
|
||||
*
|
||||
* author: Roland Conybeare, Aug 2025
|
||||
*/
|
||||
|
||||
#include "generation.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
const char * gen2str(generation x) {
|
||||
switch (x) {
|
||||
case generation::nursery: return "nursery";
|
||||
case generation::tenured: return "tenured";
|
||||
case generation::N: break;
|
||||
}
|
||||
return "?generation";
|
||||
}
|
||||
|
||||
const char * genresult2str(generation_result x) {
|
||||
switch (x) {
|
||||
case generation_result::nursery: return "nursery";
|
||||
case generation_result::tenured: return "tenured";
|
||||
case generation_result::not_found: return "not-found";
|
||||
}
|
||||
return "?generation_result";
|
||||
}
|
||||
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* generation.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue