Skip to content

Commit 8d10056

Browse files
matzclaude
andcommitted
variable.c: add object shapes for MRB_TT_OBJECT IV storage
Introduce "object shapes" (hidden classes) that share IV key layouts across objects with the same instance variable assignment order. This eliminates per-object key storage overhead. Memory savings: ~22% heap reduction for object-heavy workloads (e.g., 150k objects with 2-6 IVs). Per-object: 40->24 bytes for 2 IVs. Objects exceeding 16 IVs or using remove_instance_variable fall back to traditional iv_tbl. Co-authored-by: Claude <noreply@anthropic.com>
1 parent b4d2524 commit 8d10056

6 files changed

Lines changed: 403 additions & 12 deletions

File tree

include/mruby.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ typedef struct mrb_state {
325325

326326
struct mrb_mt_rom_list *rom_mt; /* heap-allocated ROM wrappers (freed at close) */
327327

328+
struct mrb_iv_shape *root_shape; /* root of IV shape tree */
329+
328330
void *ud; /* auxiliary data */
329331

330332
#ifdef MRB_FIXED_STATE_ATEXIT_STACK

include/mruby/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ void mrb_gc_free_gv(mrb_state*);
199199
size_t mrb_gc_mark_iv(mrb_state*, struct RObject*);
200200
void mrb_gc_free_iv(mrb_state*, struct RObject*);
201201

202+
/* IV shape tree */
203+
void mrb_init_shape(mrb_state*);
204+
void mrb_free_shape(mrb_state*);
205+
202206
/* VM */
203207
#define MRB_CI_VISIBILITY(ci) MRB_FLAGS_GET((ci)->vis, 0, 2)
204208
#define MRB_CI_SET_VISIBILITY(ci, visi) MRB_FLAGS_SET((ci)->vis, 0, 2, visi)

include/mruby/object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ struct RBasic {
2424
#define MRB_OBJ_IS_FROZEN 1
2525
#define mrb_frozen_p(o) ((o)->frozen)
2626

27+
/* Object shape flag -- when set, obj->iv is shaped, not iv_tbl* */
28+
/* Bit 5: avoids conflict with MRB_INSTANCE_TT_MASK (bits 0-4) */
29+
#define MRB_FL_OBJ_SHAPED (1 << 5)
30+
#define MRB_OBJ_SHAPED_P(o) ((o)->flags & MRB_FL_OBJ_SHAPED)
31+
2732
struct RObject {
2833
MRB_OBJECT_HEADER;
2934
struct iv_tbl *iv;

src/gc.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,9 @@ mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls)
601601
*p = RVALUE_zero;
602602
p->as.basic.tt = ttype;
603603
p->as.basic.c = cls;
604+
if (ttype == MRB_TT_OBJECT) {
605+
p->as.basic.flags |= MRB_FL_OBJ_SHAPED;
606+
}
604607
paint_partial_white(gc, &p->as.basic);
605608
return &p->as.basic;
606609
}

src/state.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@ void mrb_gc_destroy(mrb_state*, mrb_gc *gc);
2222

2323
int mrb_core_init_protect(mrb_state *mrb, void (*body)(mrb_state*, void*), void *opaque);
2424

25+
void mrb_init_shape(mrb_state*);
26+
void mrb_free_shape(mrb_state*);
27+
2528
static void
2629
init_gc_and_core(mrb_state *mrb, void *opaque)
2730
{
2831
static const struct mrb_context mrb_context_zero = { 0 };
2932

3033
mrb_gc_init(mrb, &mrb->gc);
34+
mrb_init_shape(mrb);
3135
mrb->c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
3236
*mrb->c = mrb_context_zero;
3337
mrb->root_c = mrb->c;
@@ -187,6 +191,7 @@ mrb_close(mrb_state *mrb)
187191
/* free */
188192
mrb_gc_free_gv(mrb);
189193
mrb_gc_destroy(mrb, &mrb->gc);
194+
mrb_free_shape(mrb);
190195
mrb_free_context(mrb, mrb->root_c);
191196
mrb_free_symtbl(mrb);
192197

0 commit comments

Comments
 (0)