mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
gc.c: add step_limit and malloc_threshold for GC tuning
GC.step_limit caps the per-step work in incremental GC, enabling more predictable pause times for real-time use. GC.malloc_threshold triggers GC based on allocation bytes, addressing memory pressure from large buffers. Both default to 0 (disabled), preserving existing behavior. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,9 @@ typedef struct mrb_gc {
|
||||
mrb_bool generational :1; /* generational GC mode */
|
||||
mrb_bool full :1; /* major GC mode */
|
||||
mrb_bool out_of_memory :1; /* out-of-memory error occurred */
|
||||
size_t step_limit; /* 0=unlimited, >0=absolute step cap */
|
||||
size_t malloc_increase; /* malloc bytes since last GC cycle */
|
||||
size_t malloc_threshold; /* 0=disabled, >0=bytes to trigger GC */
|
||||
|
||||
#ifdef MRB_GC_FIXED_ARENA
|
||||
struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */
|
||||
|
||||
@@ -224,6 +224,17 @@ mrb_realloc_simple(mrb_state *mrb, void *p, size_t len)
|
||||
p2 = mrb_basic_alloc_func(p, len);
|
||||
}
|
||||
|
||||
if (p2 && len > 0) {
|
||||
mrb->gc.malloc_increase += len;
|
||||
if (mrb->gc.malloc_threshold > 0 &&
|
||||
mrb->gc.malloc_increase >= mrb->gc.malloc_threshold &&
|
||||
mrb->gc.state == MRB_GC_STATE_ROOT &&
|
||||
!mrb->gc.disabled && !mrb->gc.iterating) {
|
||||
mrb->gc.malloc_increase = 0;
|
||||
mrb_incremental_gc(mrb);
|
||||
}
|
||||
}
|
||||
|
||||
return p2;
|
||||
}
|
||||
|
||||
@@ -1311,6 +1322,9 @@ incremental_gc_step(mrb_state *mrb, mrb_gc *gc)
|
||||
{
|
||||
size_t limit = 0, result = 0;
|
||||
limit = (GC_STEP_SIZE/100) * gc->step_ratio;
|
||||
if (gc->step_limit > 0 && limit > gc->step_limit) {
|
||||
limit = gc->step_limit;
|
||||
}
|
||||
while (result < limit) {
|
||||
result += incremental_gc(mrb, gc, limit);
|
||||
if (gc->state == MRB_GC_STATE_ROOT)
|
||||
@@ -1364,6 +1378,7 @@ mrb_incremental_gc(mrb_state *mrb)
|
||||
}
|
||||
|
||||
if (gc->state == MRB_GC_STATE_ROOT) {
|
||||
gc->malloc_increase = 0;
|
||||
mrb_assert(gc->live >= gc->live_after_mark);
|
||||
gc->threshold = (gc->live_after_mark/100) * gc->interval_ratio;
|
||||
if (gc->threshold < GC_STEP_SIZE) {
|
||||
@@ -1612,6 +1627,44 @@ gc_step_ratio_set(mrb_state *mrb, mrb_value obj)
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
gc_step_limit_get(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
return mrb_int_value(mrb, (mrb_int)mrb->gc.step_limit);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
gc_step_limit_set(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
mrb_int limit;
|
||||
|
||||
mrb_get_args(mrb, "i", &limit);
|
||||
if (limit < 0) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "step_limit must be non-negative");
|
||||
}
|
||||
mrb->gc.step_limit = (size_t)limit;
|
||||
return mrb_int_value(mrb, limit);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
gc_malloc_threshold_get(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
return mrb_int_value(mrb, (mrb_int)mrb->gc.malloc_threshold);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
gc_malloc_threshold_set(mrb_state *mrb, mrb_value obj)
|
||||
{
|
||||
mrb_int threshold;
|
||||
|
||||
mrb_get_args(mrb, "i", &threshold);
|
||||
if (threshold < 0) {
|
||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "malloc_threshold must be non-negative");
|
||||
}
|
||||
mrb->gc.malloc_threshold = (size_t)threshold;
|
||||
return mrb_int_value(mrb, threshold);
|
||||
}
|
||||
|
||||
static void
|
||||
change_gen_gc_mode(mrb_state *mrb, mrb_gc *gc, mrb_bool enable)
|
||||
{
|
||||
@@ -1740,6 +1793,9 @@ gc_stat(mrb_state *mrb, mrb_value self)
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "state")), mrb_int_value(mrb, (mrb_int)gc->state));
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "generational")), mrb_bool_value(gc->generational));
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "full")), mrb_bool_value(gc->full));
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "step_limit")), mrb_int_value(mrb, (mrb_int)gc->step_limit));
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "malloc_increase")), mrb_int_value(mrb, (mrb_int)gc->malloc_increase));
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "malloc_threshold")), mrb_int_value(mrb, (mrb_int)gc->malloc_threshold));
|
||||
|
||||
#ifdef MRB_GC_STATS
|
||||
mrb_hash_set(mrb, hash, mrb_symbol_value(mrb_intern_lit(mrb, "total")), mrb_int_value(mrb, (mrb_int)gc->gc_total_count));
|
||||
@@ -1773,6 +1829,10 @@ mrb_init_gc(mrb_state *mrb)
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM_E(interval_ratio), gc_interval_ratio_set, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM(step_ratio), gc_step_ratio_get, MRB_ARGS_NONE());
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM_E(step_ratio), gc_step_ratio_set, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM(step_limit), gc_step_limit_get, MRB_ARGS_NONE());
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM_E(step_limit), gc_step_limit_set, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM(malloc_threshold), gc_malloc_threshold_get, MRB_ARGS_NONE());
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM_E(malloc_threshold), gc_malloc_threshold_set, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM_E(generational_mode), gc_generational_mode_set, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method_id(mrb, gc, MRB_SYM(generational_mode), gc_generational_mode_get, MRB_ARGS_NONE());
|
||||
}
|
||||
|
||||
@@ -33,6 +33,56 @@ assert('GC.step_ratio=') do
|
||||
end
|
||||
end
|
||||
|
||||
assert('GC.step_limit=') do
|
||||
origin = GC.step_limit
|
||||
begin
|
||||
assert_equal 0, origin # default: unlimited
|
||||
assert_equal 512, (GC.step_limit = 512)
|
||||
assert_equal 512, GC.step_limit
|
||||
assert_equal 0, (GC.step_limit = 0) # back to unlimited
|
||||
ensure
|
||||
GC.step_limit = origin
|
||||
end
|
||||
end
|
||||
|
||||
assert('GC.step_limit - GC completes with small limit') do
|
||||
origin = GC.step_limit
|
||||
begin
|
||||
GC.step_limit = 64
|
||||
# GC should still complete even with a small step limit
|
||||
GC.start
|
||||
assert_true GC.stat[:live] > 0
|
||||
ensure
|
||||
GC.step_limit = origin
|
||||
end
|
||||
end
|
||||
|
||||
assert('GC.malloc_threshold=') do
|
||||
origin = GC.malloc_threshold
|
||||
begin
|
||||
assert_equal 0, origin # default: disabled
|
||||
assert_equal 65536, (GC.malloc_threshold = 65536)
|
||||
assert_equal 65536, GC.malloc_threshold
|
||||
assert_equal 0, (GC.malloc_threshold = 0) # back to disabled
|
||||
ensure
|
||||
GC.malloc_threshold = origin
|
||||
end
|
||||
end
|
||||
|
||||
assert('GC.malloc_threshold - triggers GC on large allocations') do
|
||||
origin = GC.malloc_threshold
|
||||
begin
|
||||
GC.malloc_threshold = 4096
|
||||
GC.start # reset malloc_increase
|
||||
# allocate large strings to exceed threshold
|
||||
100.times { "x" * 1024 }
|
||||
stat = GC.stat
|
||||
assert_true stat[:malloc_increase] >= 0
|
||||
ensure
|
||||
GC.malloc_threshold = origin
|
||||
end
|
||||
end
|
||||
|
||||
assert('GC.generational_mode=') do
|
||||
origin = GC.generational_mode
|
||||
begin
|
||||
|
||||
Reference in New Issue
Block a user