xo-imgui: ex2: animate GC copy step
This commit is contained in:
parent
7c846f9ad0
commit
0ad59b7c9f
4 changed files with 190 additions and 39 deletions
|
|
@ -154,6 +154,8 @@ namespace xo {
|
|||
|
||||
/** true iff GC permitted in current state **/
|
||||
bool is_gc_enabled() const { return gc_enabled_ == 0; }
|
||||
/** true iff GC has been requested **/
|
||||
bool is_gc_pending() const { return incr_gc_pending_ || full_gc_pending_; }
|
||||
/** true during (and only during) a GC cycle **/
|
||||
bool gc_in_progress() const { return runstate_.in_progress(); }
|
||||
/** @return reserved size of Nursery to-space **/
|
||||
|
|
@ -223,10 +225,14 @@ namespace xo {
|
|||
*
|
||||
* GC is enabled when number of calls to @ref enable_gc is at least as large
|
||||
* as number of calls to @ref disable_gc.
|
||||
*
|
||||
* @return true iff GC performed
|
||||
**/
|
||||
void enable_gc();
|
||||
/** same as @c this->enable_gc() followed by @c this->disable_gc() **/
|
||||
void enable_gc_once();
|
||||
bool enable_gc();
|
||||
/** same as @c this->enable_gc() followed by @c this->disable_gc()
|
||||
* @return true iff GC performed
|
||||
**/
|
||||
bool enable_gc_once();
|
||||
|
||||
// inherited from IAlloc..
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <ostream>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
namespace xo {
|
||||
namespace gc {
|
||||
|
|
@ -31,6 +32,15 @@ namespace xo {
|
|||
not_found
|
||||
};
|
||||
|
||||
inline generation valid_genresult2gen(generation_result x) {
|
||||
assert(x != generation_result::not_found);
|
||||
|
||||
if (x == generation_result::nursery)
|
||||
return generation::nursery;
|
||||
else
|
||||
return generation::tenured;
|
||||
}
|
||||
|
||||
} /*namespace gc*/
|
||||
} /*namespace xo*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1233,21 +1233,26 @@ namespace xo {
|
|||
--gc_enabled_;
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
GC::enable_gc() {
|
||||
++gc_enabled_;
|
||||
|
||||
if (gc_enabled_ == 0) {
|
||||
/* unblock gc */
|
||||
if (incr_gc_pending_)
|
||||
if (incr_gc_pending_) {
|
||||
this->request_gc(full_gc_pending_ ? generation::tenured : generation::nursery);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
GC::enable_gc_once() {
|
||||
this->enable_gc();
|
||||
bool retval = this->enable_gc();
|
||||
this->disable_gc();
|
||||
return retval;
|
||||
}
|
||||
|
||||
} /*namespace gc*/
|
||||
|
|
|
|||
|
|
@ -78,9 +78,11 @@ struct ImRect {
|
|||
*/
|
||||
struct GcGenerationDescription {
|
||||
GcGenerationDescription() = default;
|
||||
GcGenerationDescription(const char * mnemonic, std::size_t before_ckp, std::size_t after_ckp,
|
||||
GcGenerationDescription(const char * mnemonic, std::size_t tospace_scale,
|
||||
std::size_t before_ckp, std::size_t after_ckp,
|
||||
std::size_t reserved, std::size_t committed, std::size_t gc_threshold)
|
||||
: mnemonic_{mnemonic}, before_checkpoint_{before_ckp}, after_checkpoint_{after_ckp},
|
||||
: mnemonic_{mnemonic}, tospace_scale_{tospace_scale},
|
||||
before_checkpoint_{before_ckp}, after_checkpoint_{after_ckp},
|
||||
reserved_{reserved}, committed_{committed},
|
||||
gc_threshold_{gc_threshold} {}
|
||||
|
||||
|
|
@ -88,6 +90,13 @@ struct GcGenerationDescription {
|
|||
std::size_t scale() const { return std::max(committed_, gc_threshold_); }
|
||||
|
||||
const char * mnemonic_ = nullptr;
|
||||
|
||||
/** size of to-space in bytes represented on screen.
|
||||
* (note however when we animate GC, space roles have already reversed,
|
||||
* so then this will refer to old to-space = new from-space)
|
||||
**/
|
||||
std::size_t tospace_scale_ = 0;
|
||||
|
||||
std::size_t before_checkpoint_ = 0;
|
||||
std::size_t after_checkpoint_ = 0;
|
||||
std::size_t reserved_ = 0;
|
||||
|
|
@ -161,6 +170,22 @@ using xo::obj::Integer;
|
|||
using xo::rng::xoshiro256ss;
|
||||
using xo::rng::Seed;
|
||||
|
||||
/** details of a single copy event performed by GC **/
|
||||
struct GcCopyDetail {
|
||||
GcCopyDetail(std::size_t z, generation src, std::size_t src_offset, std::size_t src_space_z)
|
||||
: z_{z}, src_gen_{src}, src_offset_{src_offset}, src_space_z_{src_space_z}
|
||||
{}
|
||||
|
||||
/** object size in bytes **/
|
||||
std::size_t z_ = 0;
|
||||
/** source location **/
|
||||
generation src_gen_;
|
||||
/** offset from start of allocator **/
|
||||
std::size_t src_offset_ = 0;
|
||||
/** size of source space. could store this separately **/
|
||||
std::size_t src_space_z_ = 0;
|
||||
};
|
||||
|
||||
struct AppState {
|
||||
using GC = xo::gc::GC;
|
||||
|
||||
|
|
@ -180,6 +205,8 @@ public:
|
|||
std::vector<gp<Object>> gc_root_v_{100};
|
||||
Seed<xoshiro256ss> seed_;
|
||||
xoshiro256ss rng_{seed_};
|
||||
/** remember details for each object copied by GC, so we can animate **/
|
||||
std::vector<GcCopyDetail> copy_detail_v_;
|
||||
};
|
||||
|
||||
AppState::AppState()
|
||||
|
|
@ -225,6 +252,7 @@ GcStateDescription
|
|||
AppState::snapshot_gc_state() const {
|
||||
return GcStateDescription(GcGenerationDescription
|
||||
("N",
|
||||
this->nursery_tospace_scale(),
|
||||
gc_->nursery_before_checkpoint(),
|
||||
gc_->nursery_after_checkpoint(),
|
||||
gc_->nursery_to_reserved(),
|
||||
|
|
@ -232,6 +260,7 @@ AppState::snapshot_gc_state() const {
|
|||
gc_->nursery_before_checkpoint() + gc_->config().incr_gc_threshold_),
|
||||
GcGenerationDescription
|
||||
("T",
|
||||
this->tenured_tospace_scale(),
|
||||
gc_->tenured_before_checkpoint(),
|
||||
gc_->tenured_after_checkpoint(),
|
||||
gc_->tenured_to_reserved(),
|
||||
|
|
@ -874,7 +903,8 @@ draw_gc_state(const AppState & app_state,
|
|||
const GcStateDescription & gcstate,
|
||||
const ImRect & canvas_rect,
|
||||
ImDrawList * draw_list,
|
||||
ImRect * p_nursery_alloc_rect)
|
||||
ImRect * p_nursery_alloc_rect,
|
||||
ImRect * p_tenured_alloc_rect)
|
||||
{
|
||||
float lm = 50;
|
||||
float rm = 70;
|
||||
|
|
@ -892,7 +922,7 @@ draw_gc_state(const AppState & app_state,
|
|||
canvas_rect.y_lo() + tm),
|
||||
ImVec2(canvas_rect.x_hi() - rm,
|
||||
canvas_rect.y_lo() + tm + h + est_chart_text_height));
|
||||
/* rectangle representing allocated nursery range*/
|
||||
/* rectangle representing allocated nursery range */
|
||||
ImRect N_alloc_rect;
|
||||
float N_x1 = 0.0;
|
||||
|
||||
|
|
@ -907,7 +937,7 @@ draw_gc_state(const AppState & app_state,
|
|||
*p_nursery_alloc_rect = N_alloc_rect;
|
||||
|
||||
/* N0_to_size..N_to_scale: in bytes */
|
||||
std::size_t N_to_scale = app_state.nursery_tospace_scale();
|
||||
std::size_t N_to_scale = gcstate.gen_state_v_[gen2int(generation::nursery)].tospace_scale_;
|
||||
|
||||
/* display_w .. N0_h : viewportcoords */
|
||||
std::size_t display_w = canvas_rect.width() - lm - rm;
|
||||
|
|
@ -919,7 +949,7 @@ draw_gc_state(const AppState & app_state,
|
|||
|
||||
// now turn to Tenured space
|
||||
|
||||
std::size_t T_to_scale = app_state.tenured_tospace_scale();
|
||||
std::size_t T_to_scale = gcstate.gen_state_v_[gen2int(generation::tenured)].tospace_scale_;
|
||||
std::size_t T1_h = h;
|
||||
|
||||
/* want to put to-scale image of nursery next to to-scale image of tenured;
|
||||
|
|
@ -959,6 +989,8 @@ draw_gc_state(const AppState & app_state,
|
|||
nullptr);
|
||||
}
|
||||
|
||||
/* rectangle representing allocated tenured range */
|
||||
ImRect T_alloc_rect;
|
||||
std::size_t h_y0 = t_y1 + est_chart_text_height;
|
||||
|
||||
draw_tenured(gcstate,
|
||||
|
|
@ -967,9 +999,12 @@ draw_gc_state(const AppState & app_state,
|
|||
ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)) - TplusN_spacer,
|
||||
h_y0)),
|
||||
draw_list,
|
||||
nullptr,
|
||||
&T_alloc_rect,
|
||||
nullptr);
|
||||
|
||||
if (p_tenured_alloc_rect)
|
||||
*p_tenured_alloc_rect = T_alloc_rect;
|
||||
|
||||
/* just incremental (nursery) collections */
|
||||
draw_gc_history(gcstate,
|
||||
generation::nursery,
|
||||
|
|
@ -1013,21 +1048,84 @@ struct AnimateGcCopyCb : public xo::gc::GcCopyCallback {
|
|||
DrawState * p_draw_state_ = nullptr;
|
||||
};
|
||||
|
||||
enum class draw_state_type {
|
||||
alloc,
|
||||
animate_gc
|
||||
};
|
||||
|
||||
struct DrawState {
|
||||
up<xo::gc::GcCopyCallback> make_gc_copy_animation(AppState * app_state) {
|
||||
return std::make_unique<AnimateGcCopyCb>(app_state, this);
|
||||
}
|
||||
|
||||
draw_state_type state_type_ = draw_state_type::alloc;
|
||||
|
||||
/** when animating copy step, display objects from AppState::copy_detail_v_[i]
|
||||
* where i < .animate_copy_hi_ / 100 * AppState::copy_detail_v_.size()
|
||||
**/
|
||||
float animate_copy_hi_pct_ = 0;
|
||||
|
||||
ImDrawList * gcw_draw_list_ = nullptr;
|
||||
|
||||
/* draw area */
|
||||
/** draw area **/
|
||||
ImVec2 gcw_canvas_p0_;
|
||||
ImVec2 gcw_canvas_p1_;
|
||||
|
||||
/** rect displaying allocated nursery space **/
|
||||
ImRect gcw_nursery_alloc_rect_;
|
||||
/** rect displaying allocated tenured space **/
|
||||
ImRect gcw_tenured_alloc_rect_;
|
||||
};
|
||||
|
||||
ImRect map_src_alloc_to_screen(const GcCopyDetail & copy_detail,
|
||||
const ImRect & space_rect)
|
||||
{
|
||||
// TODO: methods on copy_detail / and/or ImPoint
|
||||
|
||||
auto [x_coord_lo, x_coord_hi] = space_rect.x_span();
|
||||
|
||||
double w0 = copy_detail.src_offset_ / static_cast<double>(copy_detail.src_space_z_);
|
||||
float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi);
|
||||
|
||||
double w1 = ((copy_detail.src_offset_ + copy_detail.z_)
|
||||
/ static_cast<double>(copy_detail.src_space_z_));
|
||||
float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi);
|
||||
|
||||
return space_rect.with_x_span(src0_x, src1_x);
|
||||
}
|
||||
|
||||
void animate_gc_copy(const AppState & app_state,
|
||||
const DrawState & draw_state,
|
||||
ImDrawList * draw_list)
|
||||
{
|
||||
const ImRect & nursery_rect = draw_state.gcw_nursery_alloc_rect_;
|
||||
const ImRect & tenured_rect = draw_state.gcw_tenured_alloc_rect_;
|
||||
|
||||
//auto [x_coord_lo, x_coord_hi] = nursery_rect.x_span();
|
||||
|
||||
std::size_t i_copy = 0;
|
||||
for (const auto & copy_detail : app_state.copy_detail_v_) {
|
||||
ImRect src_rect;
|
||||
|
||||
if (copy_detail.src_gen_ == generation::nursery) {
|
||||
src_rect = map_src_alloc_to_screen(copy_detail, nursery_rect);
|
||||
} else {
|
||||
src_rect = map_src_alloc_to_screen(copy_detail, tenured_rect);
|
||||
}
|
||||
|
||||
float hi_copy = 0.01 * draw_state.animate_copy_hi_pct_ * app_state.copy_detail_v_.size();
|
||||
float wt = i_copy / hi_copy;
|
||||
ImU32 color = IM_COL32(64, 255, static_cast<int>(64 + (128 * wt)), 255);
|
||||
|
||||
draw_list->AddRectFilled(src_rect.top_left(),
|
||||
src_rect.bottom_right(),
|
||||
color);
|
||||
|
||||
if (++i_copy >= hi_copy)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimateGcCopyCb::notify_gc_copy(std::size_t z,
|
||||
const void * src_addr,
|
||||
|
|
@ -1038,6 +1136,7 @@ AnimateGcCopyCb::notify_gc_copy(std::size_t z,
|
|||
using xo::scope;
|
||||
using xo::xtag;
|
||||
using xo::gc::generation_result;
|
||||
using xo::gc::generation;
|
||||
using xo::gc::role;
|
||||
|
||||
scope log(XO_DEBUG(false),
|
||||
|
|
@ -1047,10 +1146,6 @@ AnimateGcCopyCb::notify_gc_copy(std::size_t z,
|
|||
xtag("src_gen", src_gen),
|
||||
xtag("dest_gen", dest_gen));
|
||||
|
||||
const ImRect & nursery_rect = p_draw_state_->gcw_nursery_alloc_rect_;
|
||||
|
||||
auto [x_coord_lo, x_coord_hi] = nursery_rect.x_span();
|
||||
|
||||
auto [gen, offset, alloc] = p_app_state_->gc_->fromspace_location_of(src_addr);
|
||||
|
||||
if (gen == generation_result::not_found) {
|
||||
|
|
@ -1061,17 +1156,9 @@ AnimateGcCopyCb::notify_gc_copy(std::size_t z,
|
|||
assert(false);
|
||||
}
|
||||
|
||||
double w0 = offset / static_cast<double>(alloc);
|
||||
float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi);
|
||||
generation valid_gen = xo::gc::valid_genresult2gen(gen);
|
||||
|
||||
double w1 = (offset + z) / static_cast<double>(alloc);
|
||||
float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi);
|
||||
|
||||
ImRect src_rect = nursery_rect.with_x_span(src0_x, src1_x);
|
||||
|
||||
p_draw_state_->gcw_draw_list_->AddRectFilled(src_rect.top_left(),
|
||||
src_rect.bottom_right(),
|
||||
IM_COL32(255, 128, 128, 255));
|
||||
p_app_state_->copy_detail_v_.push_back(GcCopyDetail(z, valid_gen, offset, alloc));
|
||||
}
|
||||
|
||||
int main(int, char **)
|
||||
|
|
@ -1230,6 +1317,7 @@ int main(int, char **)
|
|||
|
||||
AppState app_state;
|
||||
DrawState draw_state;
|
||||
GcStateDescription gcstate = app_state.snapshot_gc_state();
|
||||
|
||||
app_state.gc_->add_gc_copy_callback(draw_state.make_gc_copy_animation(&app_state));
|
||||
|
||||
|
|
@ -1237,10 +1325,41 @@ int main(int, char **)
|
|||
bool done = false;
|
||||
|
||||
while (!done) {
|
||||
/** generate random alloc **/
|
||||
app_state.generate_random_mutation();
|
||||
/** on each draw cycle, app state falls into categories:
|
||||
* 1. allocation
|
||||
* multiple draw cycles because many allocations per gc.
|
||||
* 2. garbage collection
|
||||
* multiple draw cycles to animate copying process
|
||||
* Settle conflict between {GC, imgui} as to who drives event loop,
|
||||
* in favor of imgui; achieve this by copying what GC did,
|
||||
* so that we can animate it over multiple draw cycles
|
||||
**/
|
||||
|
||||
GcStateDescription gcstate = app_state.snapshot_gc_state();
|
||||
switch (draw_state.state_type_) {
|
||||
case draw_state_type::alloc:
|
||||
{
|
||||
/** generate random alloc **/
|
||||
app_state.generate_random_mutation();
|
||||
|
||||
gcstate = app_state.snapshot_gc_state();
|
||||
|
||||
/* GC may run here, in which case control reenters via AnimateGcCopyCb;
|
||||
* that callback captures copy details (per object!) in AppState
|
||||
*/
|
||||
if (app_state.gc_->enable_gc_once())
|
||||
draw_state.state_type_ = draw_state_type::animate_gc;
|
||||
|
||||
break;
|
||||
}
|
||||
case draw_state_type::animate_gc:
|
||||
{
|
||||
/* don't update gcstate while animating,
|
||||
* that would use post-GC space sizing
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** poll + handle events */
|
||||
SDL_Event event;
|
||||
|
|
@ -1310,8 +1429,10 @@ int main(int, char **)
|
|||
gcstate.gc_allocated_,
|
||||
gcstate.gc_available_);
|
||||
//ImGui::NewLine();
|
||||
ImGui::Text("promoted [%lu]",
|
||||
gcstate.total_promoted_);
|
||||
ImGui::Text("promoted [%lu] copy animation [%lu / %lu]",
|
||||
gcstate.total_promoted_,
|
||||
static_cast<std::size_t>(draw_state.animate_copy_hi_pct_ * app_state.copy_detail_v_.size() / 100),
|
||||
app_state.copy_detail_v_.size());
|
||||
|
||||
ImGui::Text("mutation [%lu] mlog [%lu]",
|
||||
gcstate.total_n_mutation_,
|
||||
|
|
@ -1335,12 +1456,21 @@ int main(int, char **)
|
|||
gcstate,
|
||||
ImRect(canvas_p0, canvas_p1),
|
||||
draw_list,
|
||||
&draw_state.gcw_nursery_alloc_rect_);
|
||||
&draw_state.gcw_nursery_alloc_rect_,
|
||||
&draw_state.gcw_tenured_alloc_rect_);
|
||||
|
||||
/* GC may run here, in which case control reenters via AnimateGcCopyCb;
|
||||
* callback will rely on loop assignments to draw_area members.
|
||||
*/
|
||||
app_state.gc_->enable_gc_once();
|
||||
if (draw_state.state_type_ == draw_state_type::animate_gc) {
|
||||
draw_state.animate_copy_hi_pct_ += 0.25;
|
||||
animate_gc_copy(app_state,
|
||||
draw_state,
|
||||
draw_list);
|
||||
|
||||
if (draw_state.animate_copy_hi_pct_ >= 100) {
|
||||
draw_state.state_type_ = draw_state_type::alloc;
|
||||
draw_state.animate_copy_hi_pct_ = 0;
|
||||
app_state.copy_detail_v_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue