From 593dc064f9e8fb8c439d0c872314de3482a10f18 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 22:34:20 -0500 Subject: [PATCH] xo-allod: + per-type stats + pretty printing --- include/xo/alloc/ArenaAlloc.hpp | 4 + include/xo/alloc/Forwarding1.hpp | 1 + include/xo/alloc/GC.hpp | 8 ++ include/xo/alloc/GcStatistics.hpp | 12 +-- include/xo/alloc/ListAlloc.hpp | 9 ++ include/xo/alloc/Object.hpp | 6 ++ include/xo/alloc/ObjectStatistics.hpp | 86 +++++++++++++++++ src/alloc/ArenaAlloc.cpp | 39 ++++++++ src/alloc/CMakeLists.txt | 1 + src/alloc/Forwarding1.cpp | 6 ++ src/alloc/GC.cpp | 128 +++++++------------------- src/alloc/GcStatistics.cpp | 87 +++++++++++++++++ src/alloc/ListAlloc.cpp | 11 +++ src/alloc/Object.cpp | 12 +++ src/alloc/ObjectStatistics.cpp | 73 +++++++++++++++ utest/LinearAlloc.test.cpp | 87 ----------------- 16 files changed, 379 insertions(+), 191 deletions(-) create mode 100644 include/xo/alloc/ObjectStatistics.hpp create mode 100644 src/alloc/ObjectStatistics.cpp delete mode 100644 utest/LinearAlloc.test.cpp diff --git a/include/xo/alloc/ArenaAlloc.hpp b/include/xo/alloc/ArenaAlloc.hpp index 12033646..9f41a8f1 100644 --- a/include/xo/alloc/ArenaAlloc.hpp +++ b/include/xo/alloc/ArenaAlloc.hpp @@ -6,6 +6,7 @@ #pragma once #include "IAlloc.hpp" +#include "ObjectStatistics.hpp" namespace xo { namespace gc { @@ -48,6 +49,9 @@ namespace xo { std::byte * free_ptr() const { return free_ptr_; } void set_free_ptr(std::byte * x); + void capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const; + // inherited from IAlloc... virtual const std::string & name() const final override { return name_; } diff --git a/include/xo/alloc/Forwarding1.hpp b/include/xo/alloc/Forwarding1.hpp index 17a38df2..cf54597d 100644 --- a/include/xo/alloc/Forwarding1.hpp +++ b/include/xo/alloc/Forwarding1.hpp @@ -13,6 +13,7 @@ namespace xo { // inherited from Object.. virtual TaggedPtr self_tp() const final override; + virtual void display(std::ostream & os) const final override; virtual bool _is_forwarded() const final override { return true; } virtual Object * _offset_destination(Object * src) const final override; virtual Object * _destination() final override; diff --git a/include/xo/alloc/GC.hpp b/include/xo/alloc/GC.hpp index 4fb9f518..daef1881 100644 --- a/include/xo/alloc/GC.hpp +++ b/include/xo/alloc/GC.hpp @@ -42,6 +42,8 @@ namespace xo { std::size_t initial_tenured_z_ = 0; /** true to permit incremental garbage collection **/ bool allow_incremental_gc_ = true; + /** true to report statistics **/ + bool stats_flag_ = false; /** true to enable debug logging **/ bool debug_flag_ = false; }; @@ -207,6 +209,8 @@ namespace xo { void swap_mutation_log(); /** swap roles of FromSpace/ToSpace **/ void swap_spaces(generation g); + /** scan to-space for object statistics before GC */ + void capture_object_statistics(generation upto, capture_phase phase); /** copy object **/ void copy_object(Object ** addr, generation upto, ObjectStatistics * object_stats); /** copy everything reachable from global gc roots **/ @@ -273,6 +277,10 @@ namespace xo { /** allocation/collection counters **/ GcStatistics gc_statistics_; + /** optional per-object-type counters. snapshot at beginning of collection cycle **/ + std::array object_statistics_sab_; + /** optional per-object-type counters. snapshot at end of collection cycle **/ + std::array object_statistics_sae_; /** trigger full GC whenever this much data arrives in tenured generation **/ std::size_t full_gc_threshold_ = 0; diff --git a/include/xo/alloc/GcStatistics.hpp b/include/xo/alloc/GcStatistics.hpp index 01cfc80b..24cac461 100644 --- a/include/xo/alloc/GcStatistics.hpp +++ b/include/xo/alloc/GcStatistics.hpp @@ -6,20 +6,13 @@ #pragma once #include "generation.hpp" +#include "xo/reflect/TypeDescr.hpp" #include "xo/indentlog/print/pretty.hpp" #include #include namespace xo { namespace gc { - /** @class ObjectStatistics - * @brief placeholder for type-driven allocation statistics - * - * Passed to @ref Object::deep_move for example - **/ - class ObjectStatistics { - }; - /** @class PerGenerationStatistics * @brief garbage collection statistics for particular GC generation **/ @@ -103,9 +96,6 @@ namespace xo { * (N0 -> N1 when reported; cumulative across GCs) **/ std::size_t n_xckp_mutation_ = 0; - - /** per-type statistics (placeholder) **/ - ObjectStatistics per_type_stats_; }; inline std::ostream & operator<< (std::ostream & os, const GcStatistics & x) { diff --git a/include/xo/alloc/ListAlloc.hpp b/include/xo/alloc/ListAlloc.hpp index db1a2df7..f9c20d00 100644 --- a/include/xo/alloc/ListAlloc.hpp +++ b/include/xo/alloc/ListAlloc.hpp @@ -6,6 +6,7 @@ #pragma once #include "IAlloc.hpp" +#include "ObjectStatistics.hpp" #include #include #include @@ -43,6 +44,14 @@ namespace xo { /** current free pointer **/ std::byte * free_ptr() const; + /** scan space (must not contain forwarding pointers, because loses size info) + * + gather stats by object type + * + * See @ref Object::self_tp + **/ + void capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const; + // inherited from IAlloc.. virtual const std::string & name() const final override; diff --git a/include/xo/alloc/Object.hpp b/include/xo/alloc/Object.hpp index 47b26261..0f50bfed 100644 --- a/include/xo/alloc/Object.hpp +++ b/include/xo/alloc/Object.hpp @@ -157,6 +157,9 @@ namespace xo { **/ virtual TaggedPtr self_tp() const = 0; + /** print on stream @p os **/ + virtual void display(std::ostream & os) const = 0; + // GC support /** true iff this object represents a forwarding pointer. @@ -244,6 +247,9 @@ namespace xo { rhs.ptr()); } + std::ostream & + operator<< (std::ostream & os, gp x); + /** @class Cpof * @brief argument to operator new used for garbage collector evacuation phase * diff --git a/include/xo/alloc/ObjectStatistics.hpp b/include/xo/alloc/ObjectStatistics.hpp new file mode 100644 index 00000000..ced3b463 --- /dev/null +++ b/include/xo/alloc/ObjectStatistics.hpp @@ -0,0 +1,86 @@ +/* file ObjectStatistics.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#pragma once + +#include "xo/indentlog/print/pretty.hpp" +#include +#include + +namespace xo { + namespace reflect { class TypeDescrBase; } + + namespace gc { + enum class capture_phase { + /** snapshot-at-beginning **/ + sab, + /** snapshot-at-end **/ + sae, + }; + + /** @class PerObjectTypeStatistics + * @brief statistics for a particular object type + * + * Gathered for each leaf type descended from xo::obj::Object. + * See @ref xo::obj::Object::self_tp + * + * See @ref GC::capture_object_statistics + * (gathers @ref scanned_n_, @ref scanned_z_) + **/ + struct PerObjectTypeStatistics { + using TypeDescr = xo::reflect::TypeDescrBase const *; + + void display(std::ostream & os) const; + + /** stats here are for objects of this type **/ + TypeDescr td_ = nullptr; + /** number of objects scanned **/ + std::size_t scanned_n_ = 0; + /** number of bytes scanned **/ + std::size_t scanned_z_ = 0; + /** number of objects surviving **/ + std::size_t survive_n_ = 0; + /** number of bytes from surviving objects **/ + std::size_t survive_z_ = 0; + }; + + inline std::ostream & operator<< (std::ostream & os, const PerObjectTypeStatistics & x) { + x.display(os); + return os; + } + + /** @class ObjectStatistics + * @brief placeholder for type-driven allocation statistics + * + * Passed to @ref Object::deep_move for example + **/ + struct ObjectStatistics { + void display(std::ostream & os) const; + + /** per-object-type statistics, indexed by TypeId **/ + std::vector per_type_stats_v_; + }; + + inline std::ostream & operator<< (std::ostream & os, const ObjectStatistics & x) { + x.display(os); + return os; + } + + } /*namespace gc*/ + + namespace print { + template <> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::PerObjectTypeStatistics &); + }; + + template <> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::ObjectStatistics &); + }; + } /*namespace print*/ +} /*namespace xo*/ + +/* end ObjectStatistics.hpp */ diff --git a/src/alloc/ArenaAlloc.cpp b/src/alloc/ArenaAlloc.cpp index c3f70d8c..ae67365a 100644 --- a/src/alloc/ArenaAlloc.cpp +++ b/src/alloc/ArenaAlloc.cpp @@ -4,6 +4,8 @@ */ #include "ArenaAlloc.hpp" +#include "Object.hpp" +#include "ObjectStatistics.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -64,6 +66,43 @@ namespace xo { } } + void + ArenaAlloc::capture_object_statistics(capture_phase phase, + ObjectStatistics * p_dest) const + { + using xo::reflect::TaggedPtr; + + std::byte * p = lo_; + + while (p < free_ptr_) { + Object * obj = reinterpret_cast(p); + TaggedPtr tp = obj->self_tp(); + std::size_t z = obj->_shallow_size(); + std::uint32_t id = tp.td()->id().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(); + 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_); + } + std::size_t ArenaAlloc::size() const { return limit_ - lo_; diff --git a/src/alloc/CMakeLists.txt b/src/alloc/CMakeLists.txt index 180b936f..55b8641c 100644 --- a/src/alloc/CMakeLists.txt +++ b/src/alloc/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS ListAlloc.cpp GC.cpp GcStatistics.cpp + ObjectStatistics.cpp Object.cpp Forwarding1.cpp ) diff --git a/src/alloc/Forwarding1.cpp b/src/alloc/Forwarding1.cpp index 2122d248..5a941c82 100644 --- a/src/alloc/Forwarding1.cpp +++ b/src/alloc/Forwarding1.cpp @@ -23,6 +23,12 @@ namespace xo { return Reflect::make_tp(const_cast(this)); } + void + Forwarding1::display(std::ostream & os) const + { + os << ""; + } + Object * Forwarding1::_offset_destination(Object * src) const { diff --git a/src/alloc/GC.cpp b/src/alloc/GC.cpp index d3b3c978..afac26df 100644 --- a/src/alloc/GC.cpp +++ b/src/alloc/GC.cpp @@ -12,91 +12,6 @@ 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); - - 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 << ""; - } - - 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(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(upto)].update_snapshot(after_z); - } - - void - GcStatistics::display(std::ostream & os) const - { - os << ""; - } - - void - GcStatisticsExt::display(std::ostream & os) const - { - os << ""; - } - bool MutationLogEntry::is_child_forwarded() const { @@ -542,6 +457,22 @@ namespace xo { } /*swap_spaces*/ + void + GC::capture_object_statistics(generation upto, capture_phase phase) + { + /* scan nursery */ + this->nursery_[role2int(role::to_space)]->capture_object_statistics + (phase, + &object_statistics_sab_[gen2int(generation::nursery)]); + + if (upto == generation::tenured) { + /* scan tenured */ + this->tenured_[role2int(role::to_space)]->capture_object_statistics + (phase, + &object_statistics_sab_[gen2int(generation::tenured)]); + } + } + void GC::copy_object(Object ** pp_object, generation upto, ObjectStatistics * object_stats) { @@ -566,7 +497,7 @@ namespace xo { GC::copy_globals(generation upto) { for (Object ** pp_root : gc_root_v_) { - this->copy_object(pp_root, upto, &gc_statistics_.per_type_stats_); + this->copy_object(pp_root, upto, &object_statistics_sae_[gen2int(upto)]); } } @@ -802,7 +733,7 @@ namespace xo { if (upto == generation::tenured) { log && log("TODO: forward mutation log for full GC"); } else { - this->incremental_gc_forward_mlog(&gc_statistics_.per_type_stats_); + this->incremental_gc_forward_mlog(&object_statistics_sae_[gen2int(generation::nursery)]); } } @@ -858,7 +789,7 @@ namespace xo { void GC::execute_gc(generation upto) { - scope log(XO_DEBUG(config_.debug_flag_)); + scope log(XO_DEBUG(config_.stats_flag_)); bool full_move = (upto == generation::tenured); @@ -876,7 +807,11 @@ namespace xo { log && log(xtag("new_alloc", new_alloc)); - log && log("step 1: swap to/from roles"); + log && log("step 0: (optional) scan for object statistics"); + + this->capture_object_statistics(upto, capture_phase::sab); + + log && log("step 1 : swap to/from roles"); this->swap_spaces(upto); @@ -886,18 +821,25 @@ namespace xo { log && log("step 2b: TODO: copy pinned"); - log && log("step 3: forward mutation log"); + log && log("step 3 : forward mutation log"); this->forward_mutation_log(upto); - log && log("step 4: TODO: notify destructor log"); + log && log("step 4 : TODO: notify destructor log"); - log && log("step 5: TODO: keep reachable weak pointers"); + log && log("step 5 : TODO: keep reachable weak pointers"); - log && log("step 6: cleanup"); + log && log("step 6 : cleanup"); this->cleanup_phase(upto); + this->capture_object_statistics(upto, capture_phase::sae); + + log && log("object statistics [nursery]:"); + log && log(refrtag("stats", object_statistics_sab_[gen2int(generation::nursery)])); + log && log("object statistics [tenured]:"); + log && log(refrtag("stats", object_statistics_sab_[gen2int(generation::tenured)])); + this->runstate_ = GCRunstate(); log && log("statistics:"); diff --git a/src/alloc/GcStatistics.cpp b/src/alloc/GcStatistics.cpp index b78b693b..d54c3cf2 100644 --- a/src/alloc/GcStatistics.cpp +++ b/src/alloc/GcStatistics.cpp @@ -7,6 +7,93 @@ #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); + + 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 << ""; + } + + 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(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(upto)].update_snapshot(after_z); + } + + void + GcStatistics::display(std::ostream & os) const + { + os << ""; + } + + void + GcStatisticsExt::display(std::ostream & os) const + { + os << ""; + } + } /*namespace gc*/ + namespace print { bool ppdetail::print_pretty(const ppindentinfo & ppii, diff --git a/src/alloc/ListAlloc.cpp b/src/alloc/ListAlloc.cpp index a5ae38e7..9d727591 100644 --- a/src/alloc/ListAlloc.cpp +++ b/src/alloc/ListAlloc.cpp @@ -50,6 +50,17 @@ namespace xo { 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_) { diff --git a/src/alloc/Object.cpp b/src/alloc/Object.cpp index d3772003..b3db11ca 100644 --- a/src/alloc/Object.cpp +++ b/src/alloc/Object.cpp @@ -191,6 +191,18 @@ namespace xo { (void)fwd; } + std::ostream & + operator<< (std::ostream & os, gp x) + { + if (x.ptr()) { + x->display(os); + } else { + os << ""; + } + + return os; + } + } /*namespace xo*/ /* end Object.cpp*/ diff --git a/src/alloc/ObjectStatistics.cpp b/src/alloc/ObjectStatistics.cpp new file mode 100644 index 00000000..a69928dc --- /dev/null +++ b/src/alloc/ObjectStatistics.cpp @@ -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 << "short_name()); + else + os << rtag("td", "nullptr"); + os << rtag("scanned_n", scanned_n_) + << rtag("scanned_z", scanned_z_) + << rtag("survive_n", survive_n_) + << rtag("survive_z", survive_z_) + << ">"; + } + + void + ObjectStatistics::display(std::ostream & os) const + { + os << ""; + } + } /*namespace gc*/ + + namespace print { + bool + ppdetail::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::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 */ diff --git a/utest/LinearAlloc.test.cpp b/utest/LinearAlloc.test.cpp deleted file mode 100644 index b1909991..00000000 --- a/utest/LinearAlloc.test.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* @file LinearAlloc.test.cpp - * - * author: Roland Conybeare, Jul 2025 - */ - -#include "xo/alloc/ArenaAlloc.hpp" -#include - -namespace xo { - using xo::gc::LinearAlloc; - - namespace ut { - - namespace { - struct testcase_alloc { - testcase_alloc(std::size_t rz, std::size_t z) - : redline_z_{rz}, arena_z_{z} {} - - std::size_t redline_z_; - std::size_t arena_z_; - - }; - - std::vector - s_testcase_v = { - testcase_alloc(0, 4096) - }; - } - - - TEST_CASE("linearalloc", "[alloc]") - { - for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { - const testcase_alloc & tc = s_testcase_v[i_tc]; - - constexpr bool c_debug_flag = false; - - auto alloc = LinearAlloc::make("linearalloc", tc.redline_z_, tc.arena_z_, c_debug_flag); - - REQUIRE(alloc.get()); - REQUIRE(alloc->name() == "linearalloc"); - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == tc.arena_z_); - REQUIRE(alloc->allocated() == 0); - REQUIRE(alloc->is_before_checkpoint(alloc->free_ptr()) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == 0); - - auto free0 = alloc->free_ptr(); - - auto mem = alloc->alloc(tc.arena_z_); - - REQUIRE(mem != nullptr); - - REQUIRE(mem == free0); - - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == 0); - REQUIRE(alloc->allocated() == tc.arena_z_); - REQUIRE(alloc->is_before_checkpoint(mem) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == tc.arena_z_); - - alloc->clear(); - - REQUIRE(alloc->free_ptr() == free0); - REQUIRE(alloc->available() == tc.arena_z_); - REQUIRE(alloc->allocated() == 0); - REQUIRE(alloc->is_before_checkpoint(free0) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == 0); - - mem = alloc->alloc(1); - - auto used = sizeof(void*); - REQUIRE(alloc->size() == tc.arena_z_); - REQUIRE(alloc->available() == tc.arena_z_ - used); - REQUIRE(alloc->allocated() == used); - REQUIRE(alloc->is_before_checkpoint(free0) == false); - REQUIRE(alloc->before_checkpoint() == 0); - REQUIRE(alloc->after_checkpoint() == used); - - } - } - - } /*namespace ut */ -} /*namespace xo*/