mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
gc.c: replace gcnext gray linked list with fixed-size gray stack
remove per-object gcnext pointer from MRB_OBJECT_HEADER, saving one word (8 bytes on 64-bit) per object slot. the gray list for tri-color marking is replaced by a fixed-size stack (MRB_GRAY_STACK_SIZE=1024) in mrb_gc. when the stack overflows, a linear heap rescan recovers gray objects. object slot size: 48 -> 40 bytes (16.7% reduction on 64-bit). benchmarks show up to 12% RSS reduction on object-heavy workloads with neutral performance impact. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+7
-2
@@ -28,6 +28,10 @@ MRB_API void mrb_free_context(struct mrb_state *mrb, struct mrb_context *c);
|
||||
#define MRB_GC_ARENA_SIZE 100
|
||||
#endif
|
||||
|
||||
#ifndef MRB_GRAY_STACK_SIZE
|
||||
#define MRB_GRAY_STACK_SIZE 1024
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MRB_GC_STATE_ROOT = 0,
|
||||
MRB_GC_STATE_MARK,
|
||||
@@ -38,8 +42,9 @@ typedef struct mrb_gc {
|
||||
struct mrb_heap_page *heaps; /* all heaps pages */
|
||||
struct mrb_heap_page *free_heaps;/* heaps for allocation */
|
||||
struct mrb_heap_page *sweeps; /* page where sweep starts */
|
||||
struct RBasic *gray_list; /* list of gray objects to be traversed incrementally */
|
||||
struct RBasic *atomic_gray_list; /* list of objects to be traversed atomically */
|
||||
struct RBasic *gray_stack[MRB_GRAY_STACK_SIZE]; /* stack of gray objects */
|
||||
size_t gray_stack_top; /* top index of gray stack */
|
||||
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
|
||||
size_t live; /* count of live objects */
|
||||
size_t live_after_mark; /* old generation objects */
|
||||
size_t threshold; /* threshold to start GC */
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
MRB_BEGIN_DECL
|
||||
|
||||
/* offset of `iv` must be 3 words */
|
||||
/* offset of `iv` must match struct RObject */
|
||||
struct RHash {
|
||||
MRB_OBJECT_HEADER;
|
||||
#ifdef MRB_64BIT
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#define MRB_OBJECT_HEADER \
|
||||
struct RClass *c; \
|
||||
struct RBasic *gcnext; \
|
||||
enum mrb_vtype tt:8; \
|
||||
unsigned int gc_color:3; \
|
||||
unsigned int frozen:1; \
|
||||
@@ -39,7 +38,7 @@ struct RFiber {
|
||||
};
|
||||
|
||||
#define mrb_static_assert_object_size(st) \
|
||||
mrb_static_assert(sizeof(st) <= sizeof(void*) * 6, \
|
||||
#st " size must be within 6 words")
|
||||
mrb_static_assert(sizeof(st) <= sizeof(void*) * 5, \
|
||||
#st " size must be within 5 words")
|
||||
|
||||
#endif /* MRUBY_OBJECT_H */
|
||||
|
||||
@@ -49,7 +49,7 @@ static const mrb_irep catch_irep = {
|
||||
/* Procedure object for catch method - used to identify catch blocks in call stack */
|
||||
mrb_alignas(8)
|
||||
static const struct RProc catch_proc = {
|
||||
NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
{ &catch_irep }, NULL, { NULL }
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -487,7 +487,7 @@ mrb_dump_irep_cstruct(mrb_state *mrb, const mrb_irep *irep, uint8_t flags, FILE
|
||||
"extern\n"
|
||||
"#endif",
|
||||
initname);
|
||||
fprintf(fp, "NULL,NULL,MRB_TT_PROC,MRB_GC_RED,MRB_OBJ_IS_FROZEN,0,{&%s_irep_0},NULL,{NULL},\n}};\n", initname);
|
||||
fprintf(fp, "NULL,MRB_TT_PROC,MRB_GC_RED,MRB_OBJ_IS_FROZEN,0,{&%s_irep_0},NULL,{NULL},\n}};\n", initname);
|
||||
fputs("static void\n", fp);
|
||||
fprintf(fp, "%s_init_syms(mrb_state *mrb)\n", initname);
|
||||
fputs("{\n", fp);
|
||||
|
||||
+2
-2
@@ -4206,7 +4206,7 @@ static const mrb_irep new_irep = {
|
||||
|
||||
mrb_alignas(8)
|
||||
static const struct RProc new_proc = {
|
||||
NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
{ &new_irep }, NULL, { NULL }
|
||||
};
|
||||
|
||||
@@ -4238,7 +4238,7 @@ static const mrb_irep neq_irep = {
|
||||
|
||||
mrb_alignas(8)
|
||||
static const struct RProc neq_proc = {
|
||||
NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
{ &neq_irep }, NULL, { NULL }
|
||||
};
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ struct free_obj {
|
||||
|
||||
struct RVALUE_initializer {
|
||||
MRB_OBJECT_HEADER;
|
||||
char padding[sizeof(void*) * 4 - sizeof(uint32_t)];
|
||||
char padding[sizeof(void*) * 3];
|
||||
};
|
||||
|
||||
struct RVALUE {
|
||||
@@ -531,8 +531,12 @@ add_gray_list(mrb_gc *gc, struct RBasic *obj)
|
||||
}
|
||||
#endif
|
||||
paint_gray(obj);
|
||||
obj->gcnext = gc->gray_list;
|
||||
gc->gray_list = obj;
|
||||
if (gc->gray_stack_top < MRB_GRAY_STACK_SIZE) {
|
||||
gc->gray_stack[gc->gray_stack_top++] = obj;
|
||||
}
|
||||
else {
|
||||
gc->gray_overflow = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -914,8 +918,8 @@ root_scan_phase(mrb_state *mrb, mrb_gc *gc)
|
||||
int i, e;
|
||||
|
||||
if (!is_minor_gc(gc)) {
|
||||
gc->gray_list = NULL;
|
||||
gc->atomic_gray_list = NULL;
|
||||
gc->gray_stack_top = 0;
|
||||
gc->gray_overflow = FALSE;
|
||||
}
|
||||
|
||||
mrb_gc_mark_gv(mrb);
|
||||
@@ -964,13 +968,37 @@ root_scan_phase(mrb_state *mrb, mrb_gc *gc)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
gc_gray_rescan(mrb_state *mrb, mrb_gc *gc)
|
||||
{
|
||||
mrb_heap_page *page = gc->heaps;
|
||||
|
||||
gc->gray_overflow = FALSE;
|
||||
while (page) {
|
||||
RVALUE *p = page->objects;
|
||||
RVALUE *e = p + MRB_HEAP_PAGE_SIZE;
|
||||
for (; p < e; p++) {
|
||||
if (is_gray(&p->as.basic) && p->as.basic.tt != MRB_TT_FREE) {
|
||||
if (gc->gray_stack_top >= MRB_GRAY_STACK_SIZE) {
|
||||
gc->gray_overflow = TRUE;
|
||||
return;
|
||||
}
|
||||
gc->gray_stack[gc->gray_stack_top++] = &p->as.basic;
|
||||
}
|
||||
}
|
||||
page = page->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gc_mark_gray_list(mrb_state *mrb, mrb_gc *gc) {
|
||||
while (gc->gray_list) {
|
||||
struct RBasic *obj = gc->gray_list;
|
||||
gc->gray_list = obj->gcnext;
|
||||
obj->gcnext = NULL;
|
||||
gc_mark_children(mrb, gc, obj);
|
||||
for (;;) {
|
||||
while (gc->gray_stack_top > 0) {
|
||||
struct RBasic *obj = gc->gray_stack[--gc->gray_stack_top];
|
||||
gc_mark_children(mrb, gc, obj);
|
||||
}
|
||||
if (!gc->gray_overflow) break;
|
||||
gc_gray_rescan(mrb, gc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,11 +1007,18 @@ incremental_marking_phase(mrb_state *mrb, mrb_gc *gc, size_t limit)
|
||||
{
|
||||
size_t tried_marks = 0;
|
||||
|
||||
while (gc->gray_list && tried_marks < limit) {
|
||||
struct RBasic *obj = gc->gray_list;
|
||||
gc->gray_list = obj->gcnext;
|
||||
obj->gcnext = NULL;
|
||||
tried_marks += gc_mark_children(mrb, gc, obj);
|
||||
while (tried_marks < limit) {
|
||||
if (gc->gray_stack_top > 0) {
|
||||
struct RBasic *obj = gc->gray_stack[--gc->gray_stack_top];
|
||||
tried_marks += gc_mark_children(mrb, gc, obj);
|
||||
}
|
||||
else if (gc->gray_overflow) {
|
||||
gc_gray_rescan(mrb, gc);
|
||||
if (gc->gray_stack_top == 0) break;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tried_marks;
|
||||
@@ -1027,18 +1062,12 @@ final_marking_phase(mrb_state *mrb, mrb_gc *gc)
|
||||
#endif
|
||||
|
||||
gc_mark_gray_list(mrb, gc);
|
||||
mrb_assert(gc->gray_list == NULL);
|
||||
gc->gray_list = gc->atomic_gray_list;
|
||||
gc->atomic_gray_list = NULL;
|
||||
gc_mark_gray_list(mrb, gc);
|
||||
mrb_assert(gc->gray_list == NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
prepare_incremental_sweep(mrb_state *mrb, mrb_gc *gc)
|
||||
{
|
||||
// mrb_assert(gc->atomic_gray_list == NULL);
|
||||
// mrb_assert(gc->gray_list == NULL);
|
||||
// mrb_assert(gc->gray_stack_top == 0);
|
||||
gc->state = MRB_GC_STATE_SWEEP;
|
||||
gc->sweeps = NULL;
|
||||
gc->live_after_mark = gc->live;
|
||||
@@ -1131,7 +1160,7 @@ incremental_gc(mrb_state *mrb, mrb_gc *gc, size_t limit)
|
||||
flip_white_part(gc);
|
||||
return 0;
|
||||
case MRB_GC_STATE_MARK:
|
||||
if (gc->gray_list) {
|
||||
if (gc->gray_stack_top > 0 || gc->gray_overflow) {
|
||||
return incremental_marking_phase(mrb, gc, limit);
|
||||
}
|
||||
else {
|
||||
@@ -1190,7 +1219,8 @@ clear_all_old(mrb_state *mrb, mrb_gc *gc)
|
||||
incremental_gc_finish(mrb, gc);
|
||||
gc->generational = TRUE;
|
||||
/* The gray objects have already been painted as white */
|
||||
gc->atomic_gray_list = gc->gray_list = NULL;
|
||||
gc->gray_stack_top = 0;
|
||||
gc->gray_overflow = FALSE;
|
||||
}
|
||||
|
||||
MRB_API void
|
||||
@@ -1318,8 +1348,12 @@ mrb_write_barrier(mrb_state *mrb, struct RBasic *obj)
|
||||
mrb_assert(!is_dead(gc, obj));
|
||||
mrb_assert(is_generational(gc) || gc->state != MRB_GC_STATE_ROOT);
|
||||
paint_gray(obj);
|
||||
obj->gcnext = gc->atomic_gray_list;
|
||||
gc->atomic_gray_list = obj;
|
||||
if (gc->gray_stack_top < MRB_GRAY_STACK_SIZE) {
|
||||
gc->gray_stack[gc->gray_stack_top++] = obj;
|
||||
}
|
||||
else {
|
||||
gc->gray_overflow = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ static const mrb_irep call_irep = {
|
||||
|
||||
mrb_alignas(8)
|
||||
static const struct RProc call_proc = {
|
||||
NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT,
|
||||
{ &call_irep }, NULL, { NULL }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user