From 432e0efce2acfa5008a7a054d32d5e70eb4ab5d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 6 Aug 2025 13:53:31 -0500 Subject: [PATCH] xo-object: improve GC unittest + prep to integrate w/ xo::reflect --- include/xo/alloc/GC.hpp | 116 +-------------------- include/xo/alloc/GcStatistics.hpp | 161 ++++++++++++++++++++++++++++++ include/xo/alloc/Object.hpp | 1 + include/xo/alloc/generation.hpp | 26 +++++ src/alloc/CMakeLists.txt | 3 +- src/alloc/GC.cpp | 38 +++++++ src/alloc/GcStatistics.cpp | 68 +++++++++++++ utest/GC.test.cpp | 12 +-- 8 files changed, 305 insertions(+), 120 deletions(-) create mode 100644 include/xo/alloc/GcStatistics.hpp create mode 100644 include/xo/alloc/generation.hpp create mode 100644 src/alloc/GcStatistics.cpp diff --git a/include/xo/alloc/GC.hpp b/include/xo/alloc/GC.hpp index 6f9573f3..4fb9f518 100644 --- a/include/xo/alloc/GC.hpp +++ b/include/xo/alloc/GC.hpp @@ -6,6 +6,7 @@ #pragma once #include "ListAlloc.hpp" +#include "GcStatistics.hpp" #include "xo/indentlog/print/array.hpp" #include #include @@ -15,20 +16,6 @@ namespace xo { class Object; namespace gc { - enum class generation { - nursery, - tenured, - N - }; - - constexpr std::size_t gen2int(generation x) { return static_cast(x); } - - enum class generation_result { - nursery, - tenured, - not_found - }; - enum class role { /** nursery: generation for new objects **/ from_space, @@ -59,104 +46,6 @@ namespace xo { bool debug_flag_ = false; }; - /** @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 - **/ - class PerGenerationStatistics { - public: - /** update statistics after a GC cycle - * @param alloc_z. new allocations (since preceding GC) - * @param before_z. generation size (bytes allocated) before collection - * @param after_z. generation size after collection - * @param promote_z. bytes promoted to next generation - **/ - void include_gc(std::size_t alloc_z, std::size_t before_z, std::size_t after_z, - std::size_t promote_z); - /** update with current state (use at end of gc cycle) **/ - void update_snapshot(std::size_t after_z); - - /** @param os. write stats on this output stream **/ - void display(std::ostream & os) const; - - /** number of bytes currently in use **/ - std::size_t used_z_ = 0; - - /** number of collection cycles completed **/ - std::size_t n_gc_ = 0; - /** sum of new alloc bytes, sampled at start of each collection cycle **/ - std::size_t new_alloc_z_ = 0; - /** sum of allocated bytes sampled at beginning of each collection cycle **/ - std::size_t scanned_z_ = 0; - /** sum of bytes remaining after collection cycle **/ - std::size_t survive_z_ = 0; - /** sum of bytes promoted to next generation **/ - std::size_t promote_z_ = 0; - }; - - inline std::ostream & operator<< (std::ostream & os, const PerGenerationStatistics & x) { - x.display(os); - return os; - } - - /** @class GcStatistics - * @brief garbage collection statistics - **/ - class GcStatistics { - public: - /** update statistics after a GC cycle - * @param upto. nursery -> incremental collection; tenured -> full collection - * @param alloc_z. new allocations (since preceding GC) - * @param before_z. generation size (bytes allocated) before collection - * @param after_z. generation size after collection - * @param promote_z. bytes promoted to next generation - **/ - void include_gc(generation upto, std::size_t alloc_z, - std::size_t before_z, std::size_t after_z, std::size_t promote_z); - /** update snapshot for current state. - * Use with tenured stats after incremental gc - **/ - void update_snapshot(generation upto, std::size_t after_z); - - /** @param os. write stats on this output stream **/ - void display(std::ostream & os) const; - - /** statistics gathered across {incr, full} GCs respectively **/ - std::array(generation::N)> gen_v_; - /** total bytes allocated since inception **/ - std::size_t total_allocated_ = 0; - /** snapshot of total bytes promoted asof beginning of last gc cycle **/ - std::size_t total_promoted_sab_ = 0; - /** total bytes promoted from nursery->tenured since inception **/ - std::size_t total_promoted_ = 0; - - /** total number of mutations to already-allocated objects, - * whether or not GC needs to log them. - **/ - std::size_t n_mutation_ = 0; - /** total number of mutation eligible for logging **/ - std::size_t n_logged_mutation_ = 0; - /** total number of cross-generation mutations (tenured->nursery when reported) **/ - std::size_t n_xgen_mutation_ = 0; - /** total number of cross-checkpoint mutations (N0 -> N1 when reported) **/ - 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) { - x.display(os); - return os; - } - /** @class GCRunstate * @brief encapsulate state needed while GC is running * @@ -228,7 +117,8 @@ namespace xo { static up make(const Config & config); const GCRunstate & runstate() const { return runstate_; } - const GcStatistics & gc_statistics() const { return gc_statistics_; } + const GcStatistics & native_gc_statistics() const { return gc_statistics_; } + GcStatisticsExt get_gc_statistics() const; /** true iff GC permitted in current state **/ bool is_gc_enabled() const { return gc_enabled_ == 0; } diff --git a/include/xo/alloc/GcStatistics.hpp b/include/xo/alloc/GcStatistics.hpp new file mode 100644 index 00000000..01cfc80b --- /dev/null +++ b/include/xo/alloc/GcStatistics.hpp @@ -0,0 +1,161 @@ +/* GcStatistics.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#pragma once + +#include "generation.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 + **/ + class PerGenerationStatistics { + public: + /** update statistics after a GC cycle + * @param alloc_z. new allocations (since preceding GC) + * @param before_z. generation size (bytes allocated) before collection + * @param after_z. generation size after collection + * @param promote_z. bytes promoted to next generation + **/ + void include_gc(std::size_t alloc_z, std::size_t before_z, std::size_t after_z, + std::size_t promote_z); + /** update with current state (use at end of gc cycle) **/ + void update_snapshot(std::size_t after_z); + + /** @param os. write stats on this output stream **/ + void display(std::ostream & os) const; + + /** number of bytes currently in use **/ + std::size_t used_z_ = 0; + + /** number of collection cycles completed **/ + std::size_t n_gc_ = 0; + /** sum of new alloc bytes, sampled at start of each collection cycle **/ + std::size_t new_alloc_z_ = 0; + /** sum of allocated bytes sampled at beginning of each collection cycle **/ + std::size_t scanned_z_ = 0; + /** sum of bytes remaining after collection cycle **/ + std::size_t survive_z_ = 0; + /** sum of bytes promoted to next generation **/ + std::size_t promote_z_ = 0; + }; + + inline std::ostream & operator<< (std::ostream & os, const PerGenerationStatistics & x) { + x.display(os); + return os; + } + + /** @class GcStatistics + * @brief garbage collection statistics + **/ + class GcStatistics { + public: + /** update statistics after a GC cycle + * @param upto. nursery -> incremental collection; tenured -> full collection + * @param alloc_z. new allocations (since preceding GC) + * @param before_z. generation size (bytes allocated) before collection + * @param after_z. generation size after collection + * @param promote_z. bytes promoted to next generation + **/ + void include_gc(generation upto, std::size_t alloc_z, + std::size_t before_z, std::size_t after_z, std::size_t promote_z); + /** update snapshot for current state. + * Use with tenured stats after incremental gc + **/ + void update_snapshot(generation upto, std::size_t after_z); + + /** @param os. write stats on this output stream **/ + void display(std::ostream & os) const; + + /** statistics gathered across {incr, full} GCs respectively **/ + std::array(generation::N)> gen_v_; + /** total bytes allocated since inception **/ + std::size_t total_allocated_ = 0; + /** snapshot of total bytes promoted asof beginning of last gc cycle **/ + std::size_t total_promoted_sab_ = 0; + /** total bytes promoted from nursery->tenured since inception **/ + std::size_t total_promoted_ = 0; + + /** total number of mutations to already-allocated objects, + * whether or not GC needs to log them. + **/ + std::size_t n_mutation_ = 0; + /** total number of mutation eligible for logging (cumulative across GCs) **/ + std::size_t n_logged_mutation_ = 0; + /** total number of cross-generation mutations + * (tenured->nursery when reported; cumulative across GCs) **/ + std::size_t n_xgen_mutation_ = 0; + /** total number of cross-checkpoint mutations + * (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) { + x.display(os); + return os; + } + + /** @class GcStatisticsExt + * @brief extend GcStatistics for application convenience + **/ + class GcStatisticsExt : public GcStatistics { + public: + explicit GcStatisticsExt(const GcStatistics & x) : GcStatistics{x} {} + + /** @param os. write stats on this output stream **/ + void display(std::ostream & os) const; + + /** current capacity of nursery generation **/ + std::size_t nursery_z_ = 0; + /** current nursery survivor size **/ + std::size_t nursery_before_checkpoint_z_ = 0; + /** current nursery new alloc size **/ + std::size_t nursery_after_checkpoint_z_ = 0; + /** current capacity of tenured generation **/ + std::size_t tenured_z_ = 0; + }; + + inline std::ostream & operator<< (std::ostream & os, const GcStatisticsExt & x) { + x.display(os); + return os; + } + + } /*namespace gc*/ + + namespace print { + template <> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::PerGenerationStatistics &); + }; + + template<> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::GcStatistics &); + }; + + template<> + struct ppdetail { + static bool print_pretty(const ppindentinfo &, const xo::gc::GcStatisticsExt &); + }; + } /*namespace print*/ +} /*namespace xo*/ + +/* end GcStatistics.hpp */ diff --git a/include/xo/alloc/Object.hpp b/include/xo/alloc/Object.hpp index f8e45e84..347b5018 100644 --- a/include/xo/alloc/Object.hpp +++ b/include/xo/alloc/Object.hpp @@ -5,6 +5,7 @@ #pragma once +#include "xo/reflect/SelfTagging.hpp" #include "IAlloc.hpp" #include #include diff --git a/include/xo/alloc/generation.hpp b/include/xo/alloc/generation.hpp new file mode 100644 index 00000000..6a5a7c2e --- /dev/null +++ b/include/xo/alloc/generation.hpp @@ -0,0 +1,26 @@ +/* generation.hpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include + +namespace xo { + namespace gc { + enum class generation { + nursery, + tenured, + N + }; + + constexpr std::size_t gen2int(generation x) { return static_cast(x); } + + enum class generation_result { + nursery, + tenured, + not_found + }; + } /*namespace gc*/ +} /*namespace xo*/ + +/* end generation.hpp */ diff --git a/src/alloc/CMakeLists.txt b/src/alloc/CMakeLists.txt index bc0f919f..180b936f 100644 --- a/src/alloc/CMakeLists.txt +++ b/src/alloc/CMakeLists.txt @@ -6,11 +6,12 @@ set(SELF_SRCS ArenaAlloc.cpp ListAlloc.cpp GC.cpp + GcStatistics.cpp Object.cpp Forwarding1.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) -xo_dependency(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} reflect) #end CMakeLists.txt diff --git a/src/alloc/GC.cpp b/src/alloc/GC.cpp index 4f106779..d3b3c978 100644 --- a/src/alloc/GC.cpp +++ b/src/alloc/GC.cpp @@ -3,6 +3,7 @@ * author: Roland Conybeare, Jul 2025 */ +#include "GcStatistics.hpp" #include "GC.hpp" #include "Object.hpp" #include "xo/indentlog/scope.hpp" @@ -67,6 +68,31 @@ namespace xo { os << ""; + } + + void + GcStatisticsExt::display(std::ostream & os) const + { + os << ""; } @@ -228,6 +254,18 @@ namespace xo { return config_.debug_flag_; } + GcStatisticsExt + GC::get_gc_statistics() const + { + GcStatisticsExt retval = GcStatisticsExt(this->native_gc_statistics()); + + retval.nursery_z_ = nursery_[role2int(role::to_space)]->size(); + retval.nursery_before_checkpoint_z_ = nursery_[role2int(role::to_space)]->before_checkpoint(); + retval.tenured_z_ = tenured_[role2int(role::to_space)]->size(); + + return retval; + } + generation_result GC::fromspace_generation_of(const void * x) const { diff --git a/src/alloc/GcStatistics.cpp b/src/alloc/GcStatistics.cpp new file mode 100644 index 00000000..b78b693b --- /dev/null +++ b/src/alloc/GcStatistics.cpp @@ -0,0 +1,68 @@ +/* GcStatistics.cpp + * + * author: Roland Conybeare, Aug 2025 + */ + +#include "GcStatistics.hpp" +#include "xo/indentlog/print/pretty_vector.hpp" + +namespace xo { + namespace print { + bool + ppdetail::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::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::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_)); + } + + + } /*namespace print*/ +} /*namespace xo*/ + +/* end GcStatistics.cpp */ diff --git a/utest/GC.test.cpp b/utest/GC.test.cpp index 12445624..c36deb2e 100644 --- a/utest/GC.test.cpp +++ b/utest/GC.test.cpp @@ -47,22 +47,22 @@ namespace xo { REQUIRE(gc->gc_in_progress() == false); REQUIRE(gc->is_gc_enabled() == true); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 0); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 0); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); /* gc with empty state */ gc->request_gc(generation::nursery); REQUIRE(gc->gc_in_progress() == false); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); /* still empty state */ gc->request_gc(generation::tenured); REQUIRE(gc->gc_in_progress() == false); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 1); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1); } }