diff --git a/xo-alloc/include/xo/alloc/GC.hpp b/xo-alloc/include/xo/alloc/GC.hpp index 6f9573f3..4fb9f518 100644 --- a/xo-alloc/include/xo/alloc/GC.hpp +++ b/xo-alloc/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/xo-alloc/include/xo/alloc/GcStatistics.hpp b/xo-alloc/include/xo/alloc/GcStatistics.hpp new file mode 100644 index 00000000..01cfc80b --- /dev/null +++ b/xo-alloc/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/xo-alloc/include/xo/alloc/Object.hpp b/xo-alloc/include/xo/alloc/Object.hpp index f8e45e84..347b5018 100644 --- a/xo-alloc/include/xo/alloc/Object.hpp +++ b/xo-alloc/include/xo/alloc/Object.hpp @@ -5,6 +5,7 @@ #pragma once +#include "xo/reflect/SelfTagging.hpp" #include "IAlloc.hpp" #include #include diff --git a/xo-alloc/include/xo/alloc/generation.hpp b/xo-alloc/include/xo/alloc/generation.hpp new file mode 100644 index 00000000..6a5a7c2e --- /dev/null +++ b/xo-alloc/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/xo-alloc/src/alloc/CMakeLists.txt b/xo-alloc/src/alloc/CMakeLists.txt index bc0f919f..180b936f 100644 --- a/xo-alloc/src/alloc/CMakeLists.txt +++ b/xo-alloc/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/xo-alloc/src/alloc/GC.cpp b/xo-alloc/src/alloc/GC.cpp index 4f106779..d3b3c978 100644 --- a/xo-alloc/src/alloc/GC.cpp +++ b/xo-alloc/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/xo-alloc/src/alloc/GcStatistics.cpp b/xo-alloc/src/alloc/GcStatistics.cpp new file mode 100644 index 00000000..b78b693b --- /dev/null +++ b/xo-alloc/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/xo-alloc/utest/GC.test.cpp b/xo-alloc/utest/GC.test.cpp index 12445624..c36deb2e 100644 --- a/xo-alloc/utest/GC.test.cpp +++ b/xo-alloc/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); } } diff --git a/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp b/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp index dfeaf415..3f460a76 100644 --- a/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp +++ b/xo-indentlog/include/xo/indentlog/print/ppdetail_atomic.hpp @@ -16,7 +16,7 @@ namespace xo { // Defining this means ppdetail_atomic is not used. // In that case where not explicitly specialized ppdetail will revert to ordinary printing for a type, // instead of giving compile-time error about missing template specialization of ppdetail. -#define ppdetail_atomic ppdetail +//#define ppdetail_atomic ppdetail struct ppindentinfo { ppindentinfo(ppstate * pps, std::uint32_t ci0, std::uint32_t indent_width, bool upto) diff --git a/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp b/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp index 6a05fe14..b22fc149 100644 --- a/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp +++ b/xo-indentlog/include/xo/indentlog/print/pretty_vector.hpp @@ -6,8 +6,11 @@ #pragma once #include "pretty.hpp" +#include "array.hpp" /*printing*/ #include "pad.hpp" #include +#include +#include namespace xo { namespace print { @@ -54,5 +57,12 @@ namespace xo { return ppdetail_vector>::print_pretty(ppii, x); } }; + + template + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, const std::array & x) { + return ppdetail_vector>::print_pretty(ppii, x); + } + }; } } diff --git a/xo-object/utest/GC.test.cpp b/xo-object/utest/GC.test.cpp index 83f4d60c..9fd19613 100644 --- a/xo-object/utest/GC.test.cpp +++ b/xo-object/utest/GC.test.cpp @@ -47,8 +47,8 @@ namespace xo { .debug_flag_ = false }); - REQUIRE(gc->gc_statistics().n_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_logged_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_logged_mutation_ == 0); REQUIRE(gc.get()); @@ -82,10 +82,10 @@ namespace xo { l->assign_head(Integer::make(2)); { - REQUIRE(gc->gc_statistics().n_mutation_ == 1); - REQUIRE(gc->gc_statistics().n_logged_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_mutation_ == 1); + REQUIRE(gc->native_gc_statistics().n_logged_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_xckp_mutation_ == 0); REQUIRE(gc->mlog_size() == 0); REQUIRE(gc->is_gc_enabled() == true); @@ -113,10 +113,10 @@ namespace xo { REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::nursery); REQUIRE(gc->is_before_checkpoint(l->head().ptr()) == false); - REQUIRE(gc->gc_statistics().n_mutation_ == 2); - REQUIRE(gc->gc_statistics().n_logged_mutation_ == 1); - REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 1); + REQUIRE(gc->native_gc_statistics().n_mutation_ == 2); + REQUIRE(gc->native_gc_statistics().n_logged_mutation_ == 1); + REQUIRE(gc->native_gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_xckp_mutation_ == 1); REQUIRE(gc->mlog_size() == 1); } @@ -132,12 +132,12 @@ namespace xo { REQUIRE(gc->tospace_generation_of(l->head().ptr()) == generation_result::nursery); REQUIRE(gc->is_before_checkpoint(l->head().ptr())); - REQUIRE(gc->gc_statistics().n_mutation_ == 2); - REQUIRE(gc->gc_statistics().n_logged_mutation_ == 1); + REQUIRE(gc->native_gc_statistics().n_mutation_ == 2); + REQUIRE(gc->native_gc_statistics().n_logged_mutation_ == 1); // counters recorded when mutation created. // not modified by gc - REQUIRE(gc->gc_statistics().n_xgen_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_xckp_mutation_ == 1); + REQUIRE(gc->native_gc_statistics().n_xgen_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_xckp_mutation_ == 1); REQUIRE(gc->mlog_size() == 1); } @@ -356,9 +356,162 @@ namespace xo { this->roots_.push_back(traverse_from_object(x)); } + /** Generate some random data + mutations to verify GC behavior + * + * To setup for first GC: + * RandomMutationModel model(m, n, r, k); + * model.generate_seed_values(); + * model.generate_random_roots(gc, &rgen); + * model.generate_random_mutations(&rgen); + * + * To prepare for next GC + * model.rejuvenate_seed_values(); + * model.alter_random_roots(&rgen); + * model.generate_random_mutations(&rgen); + **/ + struct RandomMutationModel { + RandomMutationModel(std::size_t m, std::size_t n, std::size_t r, std::size_t rr, std::size_t k) + : m_{m}, n_{n}, r_{r}, rr_{rr}, k_{k} {} + + void generate_seed_values(); + void generate_random_roots(GC * gc, xoshiro256ss * p_rgen); + void generate_random_mutations(xoshiro256ss * p_rgen); + + void rejuvenate_seed_values(); + void alter_random_roots(xoshiro256ss * p_rgen); + + /* create m random list cells */ + size_t m_ = 0; + /** create n random integers, starting with value @ref start_ **/ + size_t start_ = 0; + size_t n_ = 0; + /* #of roots */ + size_t r_ = 0; + size_t rr_ = 0; + /* #of random mutations */ + size_t k_ = 0; + + /* w1[] contains some random list cells */ + std::vector> w1_; + /* w2[] has all of w1[], also contains some integers */ + std::vector> w2_; + + /* create some random roots. always pick at least one list cell */ + std::vector> root_v_; + }; + + void RandomMutationModel::generate_seed_values() + { + { + for (size_t i = 0; i < m_; ++i) { + w1_.push_back(List::cons(List::nil, List::nil)); + } + REQUIRE(w1_.size() == m_); + } + + { + std::copy(w1_.begin(), w1_.end(), std::back_inserter(w2_)); + for (size_t j = 0; j < n_; ++j) { + w2_.push_back(Integer::make((this->start_)++)); + } + REQUIRE(w2_.size() == m_ + n_); + } + } + + void RandomMutationModel::generate_random_roots(GC * gc, + xoshiro256ss * p_rgen) + { + std::size_t w1_ix = (*p_rgen)() % n_; + { + root_v_.push_back(w2_.at(w1_ix)); + for (std::size_t i = 1; i < r_; ++i) { + std::size_t w2_ix = (*p_rgen)() % (m_ + n_); + + root_v_.push_back(w2_.at(w2_ix)); + } + + REQUIRE(root_v_.size() == r_); + + for (auto & root : root_v_) + gc->add_gc_root(root.ptr_address()); + } + + } + + void RandomMutationModel::generate_random_mutations(xoshiro256ss * p_rgen) + { + for (std::size_t i = 0; i < k_; ++i) { + /* pick a root list cell at random */ + gp l1 = w1_.at((*p_rgen)() % w1_.size()); + + if ((*p_rgen)() % 2 == 0) { + /* pick another root list cell at random, and link it to l1 */ + gp l2 = w1_.at((*p_rgen)() % w1_.size()); + + l1->assign_rest(l2); + } else { + /* pick a value at random (could be list or integer), + * assign to head + */ + gp x2 = w2_.at((*p_rgen)() % w2_.size()); + + l1->assign_head(x2); + } + } + } + + void RandomMutationModel::rejuvenate_seed_values() + { + for (std::size_t i = 0; i < w1_.size(); ++i) { + if (w1_.at(i)->_is_forwarded()) { + /* w[i] survived GC */ + w1_[i] = dynamic_cast(w1_[i]->_destination()); + } else { + /* w[i] is garbage, replace */ + w1_[i] = List::cons(List::nil, List::nil); + } + } + + for (std::size_t j = 0; j < w2_.size(); ++j) { + if (w2_.at(j)->_is_forwarded()) { + /* w2[i] survived GC */ + w2_[j] = dynamic_cast(w2_[j]->_destination()); + } else { + /* w2[j] is garbage, replace */ + w2_[j] = Integer::make((this->start_)++); + } + } + } + + void RandomMutationModel::alter_random_roots(xoshiro256ss * p_rgen) + { + /* replace a root value rr times */ + for (std::size_t i = 0; i < rr_; ++i) { + /* choose new root value at random */ + gp new_root; + { + std::size_t j = (*p_rgen)() % (w1_.size() + w2_.size()); + + if (j < w1_.size()) + new_root = w1_.at(j); + else + new_root = w2_.at(j - w1_.size()); + } + + /* choose a root to replace at random */ + std::size_t j = (*p_rgen)() % root_v_.size(); + + root_v_[j] = new_root; + } + } + struct testcase_stresstest { - testcase_stresstest(std::size_t nz, std::size_t tz, std::size_t m, std::size_t n, std::size_t r, std::size_t k, bool debug_flag) - : nursery_z_{nz}, tenured_z_{tz}, m_{m}, n_{n}, r_{r}, k_{k}, debug_flag_{debug_flag} + testcase_stresstest(std::size_t nz, std::size_t tz, + std::size_t m, std::size_t n, + std::size_t r, std::size_t rr, std::size_t k, + bool gc_stats_flag, bool debug_flag) + : nursery_z_{nz}, tenured_z_{tz}, m_{m}, n_{n}, r_{r}, rr_{rr}, k_{k}, + gc_stats_flag_{gc_stats_flag}, debug_flag_{debug_flag} {} std::size_t nursery_z_; @@ -370,15 +523,19 @@ namespace xo { std::size_t n_; /* #of gc roots to create */ std::size_t r_; + /* #of gc roots to replace between cycles */ + std::size_t rr_; /* #of random mutations */ std::size_t k_; - bool debug_flag_; + bool gc_stats_flag_ = false; + bool debug_flag_ = false; }; std::vector s_testcase_v = { - testcase_stresstest(1024, 1024, 3, 7, 5, 10, true) + /* nz tz m n r rr k stats, debug */ + testcase_stresstest(1024, 1024, 3, 7, 5, 2, 10, true, false) }; } /*namespace*/ @@ -387,7 +544,7 @@ namespace xo { for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { const testcase_stresstest & tc = s_testcase_v[i_tc]; - scope log(XO_DEBUG(tc.debug_flag_)); + scope log(XO_DEBUG(tc.gc_stats_flag_)); up gc = GC::make( { @@ -396,8 +553,8 @@ namespace xo { .debug_flag_ = tc.debug_flag_ }); - REQUIRE(gc->gc_statistics().n_mutation_ == 0); - REQUIRE(gc->gc_statistics().n_logged_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_mutation_ == 0); + REQUIRE(gc->native_gc_statistics().n_logged_mutation_ == 0); REQUIRE(gc.get()); @@ -419,92 +576,50 @@ namespace xo { //std::cerr << "seed=" << seed << std::endl; auto rgen = xoshiro256ss(seed); - /* create m random list cells */ - size_t m = tc.m_; - /* create n random integers */ - size_t n = tc.n_; - /* #of roots */ - size_t r = tc.r_; - /* #of random mutations */ - size_t k = tc.k_; + REQUIRE(tc.m_ > 0); + REQUIRE(tc.n_ > 0); + REQUIRE(tc.r_ > 0); - REQUIRE(m > 0); - REQUIRE(n > 0); + RandomMutationModel data_model(tc.m_, tc.n_, tc.r_, tc.rr_, tc.k_); - /* w1[] contains some random list cells */ - std::vector> w1; - { - for (size_t i = 0; i < m; ++i) { - w1.push_back(List::cons(List::nil, List::nil)); - } - REQUIRE(w1.size() == m); - } - - /* w2[] has all of w1[], also contains some integers */ - std::vector> w2; - { - std::copy(w1.begin(), w1.end(), std::back_inserter(w2)); - for (size_t j = 0; j < n; ++j) { - w2.push_back(Integer::make(j)); - } - REQUIRE(w2.size() == m + n); - } - - /* create some random roots. always pick at least one list cell */ - std::vector> root_v; - std::size_t w1_ix = rgen() % n; - { - root_v.push_back(w2.at(w1_ix)); - for (std::size_t i = 1; i < r; ++i) { - std::size_t w2_ix = rgen() % (m + n); - - root_v.push_back(w2.at(w2_ix)); + for (std::size_t cycle = 0; cycle < 2; ++cycle) { + if (cycle == 0) { + data_model.generate_seed_values(); + data_model.generate_random_roots(gc.get(), &rgen); + } else { + /* figure out values in {data_model_.w1_, data_model_.w2_} that + * survived GC; keep these. Discard the remainder. + * don't want these as roots, because that would alter the behavior of GC. + * + * (For example want to verify behavior of GC w.r.t. cells that are alive only + * because of a mutation) + */ + data_model.rejuvenate_seed_values(); + data_model.alter_random_roots(&rgen); } - for (auto & root : root_v) - gc->add_gc_root(root.ptr_address()); + data_model.generate_random_mutations(&rgen); + + log && log(xtag("cycle", cycle), + xtag("stats.before", gc->get_gc_statistics())); + + /* make model for contents of w2[] - baseline for post-GC comparison */ + ObjectGraphModel from_model; + from_model.from_root_vector(data_model.root_v_); + + gc->request_gc(generation::nursery); + + /* collector cycle changed object addresses. + * build a new object model, and verify consistency with from_model + */ + ObjectGraphModel to_model; + to_model.from_root_vector(data_model.root_v_); + + REQUIRE(ObjectGraphModel::verify_equal_models(from_model, to_model)); + + log && log(xtag("cycle", cycle), + xtag("stats.after", gc->get_gc_statistics())); } - - /* random mutations -- these will get logged */ - { - for (std::size_t i = 0; i < k; ++i) { - /* pick a list cell at random */ - gp l1 = w1.at(rgen() % w1.size()); - - if (rgen() % 2 == 0) { - /* pick another list cell at random, and link it to l1 */ - gp l2 = w1.at(rgen() % w1.size()); - - l1->assign_rest(l2); - } else { - /* pick a value at random (could be list or integer), - * assign to head - */ - gp x2 = w2.at(rgen() % w2.size()); - - l1->assign_head(x2); - } - } - } - - log && log("stats.before", gc->gc_statistics()); - - /* make model for contents of w2[] */ - ObjectGraphModel from_model; - from_model.from_root_vector(root_v); - - gc->request_gc(generation::nursery); - - /* collector cycle changed object addresses. - * build a new object model, and verify that they're equivalent - */ - - ObjectGraphModel to_model; - to_model.from_root_vector(root_v); - - REQUIRE(ObjectGraphModel::verify_equal_models(from_model, to_model)); - - log && log("stats.after", gc->gc_statistics()); } } } /*namespace ut*/ diff --git a/xo-object/utest/List.test.cpp b/xo-object/utest/List.test.cpp index d7812010..66409be7 100644 --- a/xo-object/utest/List.test.cpp +++ b/xo-object/utest/List.test.cpp @@ -103,8 +103,8 @@ namespace xo { */ gc->request_gc(generation::nursery); - 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); REQUIRE(gc->allocated() == expected_alloc_z); /* verify GC preserved list structure and contents */ @@ -128,8 +128,8 @@ namespace xo { /* every has survived one GC cycle. collect again should promote */ gc->request_gc(generation::nursery); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 2); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 2); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 0); REQUIRE(gc->allocated() == expected_alloc_z); /* verify GC preserved list structure and contents */ @@ -150,12 +150,12 @@ namespace xo { } } - REQUIRE(gc->gc_statistics().total_promoted_ == gc->allocated()); + REQUIRE(gc->native_gc_statistics().total_promoted_ == gc->allocated()); gc->request_gc(generation::tenured); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 2); - REQUIRE(gc->gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::nursery)].n_gc_ == 2); + REQUIRE(gc->native_gc_statistics().gen_v_[gen2int(generation::tenured)].n_gc_ == 1); REQUIRE(gc->allocated() == expected_alloc_z); /* verify GC preserved list structure and contents */ @@ -176,7 +176,7 @@ namespace xo { } } - log && log("stats", gc->gc_statistics()); + log && log("stats", gc->native_gc_statistics()); } } } /*TEST_CASE(List, ..)*/ @@ -250,8 +250,8 @@ namespace xo { gc->request_gc(generation::nursery); - 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); REQUIRE(gc->allocated() == expected_alloc_z); /* verify GC preserved list structure and contents */ diff --git a/xo-object/utest/String.test.cpp b/xo-object/utest/String.test.cpp index 13f7847e..6b05c14c 100644 --- a/xo-object/utest/String.test.cpp +++ b/xo-object/utest/String.test.cpp @@ -79,10 +79,10 @@ namespace xo { 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); REQUIRE(gc->allocated() == 0); - REQUIRE(gc->gc_statistics().total_allocated_ == expected_alloc_z); + REQUIRE(gc->native_gc_statistics().total_allocated_ == expected_alloc_z); } } } @@ -140,8 +140,8 @@ namespace xo { /* gc has a bunch of string objects; all are roots + should be preserved */ gc->request_gc(generation::nursery); - 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); REQUIRE(gc->allocated() == expected_alloc_z);