mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
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>
This commit is contained in:
+385
-13
@@ -239,6 +239,287 @@ iv_free(mrb_state *mrb, iv_tbl *t)
|
||||
mrb_free(mrb, t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Object Shape (Hidden Class) structures.
|
||||
*
|
||||
* A shape describes the IV layout of an object: which syms are stored
|
||||
* at which indices. Shapes form a tree rooted at the empty root shape.
|
||||
* Each child adds one IV (its "edge" sym). Objects sharing the same
|
||||
* set of IVs (assigned in the same order) share the same shape,
|
||||
* eliminating per-object key storage.
|
||||
*
|
||||
* Only MRB_TT_OBJECT instances are shaped. RClass, RHash, etc. keep
|
||||
* traditional iv_tbl.
|
||||
*/
|
||||
|
||||
/* Maximum IV count before de-shaping to iv_tbl */
|
||||
#define MRB_SHAPE_MAX_IVS 16
|
||||
|
||||
/* Shape descriptor -- shared across objects with same IV layout */
|
||||
typedef struct mrb_iv_shape {
|
||||
struct mrb_iv_shape *parent; /* parent shape (one fewer IV) */
|
||||
struct mrb_iv_shape *children; /* linked list of child shapes */
|
||||
struct mrb_iv_shape *sibling; /* next child of same parent */
|
||||
mrb_sym edge; /* IV sym added from parent */
|
||||
uint16_t count; /* number of IV slots */
|
||||
} mrb_iv_shape;
|
||||
|
||||
/* Per-object shaped IV storage (allocated via struct hack) */
|
||||
typedef struct mrb_shaped_iv {
|
||||
mrb_iv_shape *shape;
|
||||
mrb_value values[1]; /* shape->count elements */
|
||||
} mrb_shaped_iv;
|
||||
|
||||
/* Create the empty root shape */
|
||||
static mrb_iv_shape*
|
||||
shape_root(mrb_state *mrb)
|
||||
{
|
||||
mrb_iv_shape *s = (mrb_iv_shape*)mrb_calloc(mrb, 1, sizeof(mrb_iv_shape));
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Find a child shape with the given edge sym */
|
||||
static mrb_iv_shape*
|
||||
shape_find_child(mrb_iv_shape *shape, mrb_sym sym)
|
||||
{
|
||||
mrb_iv_shape *c = shape->children;
|
||||
while (c) {
|
||||
if (c->edge == sym) return c;
|
||||
c = c->sibling;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find or create a child shape for adding sym */
|
||||
static mrb_iv_shape*
|
||||
shape_transition(mrb_state *mrb, mrb_iv_shape *shape, mrb_sym sym)
|
||||
{
|
||||
mrb_iv_shape *child = shape_find_child(shape, sym);
|
||||
if (child) return child;
|
||||
|
||||
/* create new child shape */
|
||||
child = (mrb_iv_shape*)mrb_malloc(mrb, sizeof(mrb_iv_shape));
|
||||
child->parent = shape;
|
||||
child->children = NULL;
|
||||
child->sibling = shape->children;
|
||||
child->edge = sym;
|
||||
child->count = shape->count + 1;
|
||||
shape->children = child;
|
||||
return child;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up sym in shape by walking the parent chain.
|
||||
* Returns the value index (0-based), or -1 if not found.
|
||||
*/
|
||||
static int
|
||||
shape_lookup(mrb_iv_shape *shape, mrb_sym sym)
|
||||
{
|
||||
mrb_iv_shape *s = shape;
|
||||
while (s->count > 0) {
|
||||
if (s->edge == sym) return s->count - 1;
|
||||
s = s->parent;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Recursively free all shapes in the tree */
|
||||
static void
|
||||
shape_free_tree(mrb_state *mrb, mrb_iv_shape *shape)
|
||||
{
|
||||
mrb_iv_shape *c = shape->children;
|
||||
while (c) {
|
||||
mrb_iv_shape *next = c->sibling;
|
||||
shape_free_tree(mrb, c);
|
||||
c = next;
|
||||
}
|
||||
mrb_free(mrb, shape);
|
||||
}
|
||||
|
||||
/* Allocate a mrb_shaped_iv with room for count values */
|
||||
static mrb_shaped_iv*
|
||||
shaped_iv_alloc(mrb_state *mrb, mrb_iv_shape *shape)
|
||||
{
|
||||
size_t sz = offsetof(mrb_shaped_iv, values) +
|
||||
sizeof(mrb_value) * shape->count;
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)mrb_malloc(mrb, sz);
|
||||
siv->shape = shape;
|
||||
return siv;
|
||||
}
|
||||
|
||||
/* Convert a shaped object back to traditional iv_tbl (de-shape) */
|
||||
static void
|
||||
shaped_to_iv_tbl(mrb_state *mrb, struct RObject *obj)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
iv_tbl *t = NULL;
|
||||
|
||||
if (siv) {
|
||||
mrb_iv_shape *shape = siv->shape;
|
||||
|
||||
if (shape->count > 0) {
|
||||
/* reconstruct keys from parent chain */
|
||||
mrb_sym keys[MRB_SHAPE_MAX_IVS];
|
||||
mrb_iv_shape *s = shape;
|
||||
while (s->count > 0) {
|
||||
keys[s->count - 1] = s->edge;
|
||||
s = s->parent;
|
||||
}
|
||||
|
||||
t = iv_new(mrb);
|
||||
for (int i = 0; i < shape->count; i++) {
|
||||
if (!mrb_undef_p(siv->values[i])) {
|
||||
iv_put(mrb, t, keys[i], siv->values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
mrb_free(mrb, siv);
|
||||
}
|
||||
obj->iv = t;
|
||||
obj->flags &= ~MRB_FL_OBJ_SHAPED;
|
||||
}
|
||||
|
||||
/* --- Shaped IV operations --- */
|
||||
|
||||
static void
|
||||
shaped_iv_set(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value v)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
mrb_iv_shape *shape = siv ? siv->shape : mrb->root_shape;
|
||||
|
||||
/* check if sym already exists in current shape */
|
||||
int idx = shape_lookup(shape, sym);
|
||||
if (idx >= 0) {
|
||||
siv->values[idx] = v;
|
||||
return;
|
||||
}
|
||||
|
||||
/* transition to new shape */
|
||||
mrb_iv_shape *new_shape = shape_transition(mrb, shape, sym);
|
||||
|
||||
/* de-shape if too many IVs */
|
||||
if (new_shape->count > MRB_SHAPE_MAX_IVS) {
|
||||
shaped_to_iv_tbl(mrb, obj);
|
||||
if (!obj->iv) {
|
||||
obj->iv = iv_new(mrb);
|
||||
}
|
||||
iv_put(mrb, obj->iv, sym, v);
|
||||
return;
|
||||
}
|
||||
|
||||
/* allocate new shaped_iv with room for new shape */
|
||||
mrb_shaped_iv *new_siv = shaped_iv_alloc(mrb, new_shape);
|
||||
|
||||
/* copy old values (they are a prefix) */
|
||||
if (siv) {
|
||||
memcpy(new_siv->values, siv->values,
|
||||
sizeof(mrb_value) * shape->count);
|
||||
mrb_free(mrb, siv);
|
||||
}
|
||||
/* new IV goes in the last slot */
|
||||
new_siv->values[new_shape->count - 1] = v;
|
||||
|
||||
obj->iv = (iv_tbl*)new_siv;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
shaped_iv_get(struct RObject *obj, mrb_sym sym)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
if (!siv) return mrb_nil_value();
|
||||
int idx = shape_lookup(siv->shape, sym);
|
||||
if (idx >= 0 && !mrb_undef_p(siv->values[idx]))
|
||||
return siv->values[idx];
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
static mrb_bool
|
||||
shaped_iv_defined(struct RObject *obj, mrb_sym sym)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
if (!siv) return FALSE;
|
||||
int idx = shape_lookup(siv->shape, sym);
|
||||
if (idx >= 0 && !mrb_undef_p(siv->values[idx]))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
shaped_iv_foreach(mrb_state *mrb, struct RObject *obj,
|
||||
mrb_iv_foreach_func *func, void *p)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
if (!siv) return;
|
||||
mrb_iv_shape *shape = siv->shape;
|
||||
if (shape->count == 0) return;
|
||||
|
||||
/* reconstruct keys from parent chain */
|
||||
mrb_sym keys[MRB_SHAPE_MAX_IVS];
|
||||
mrb_iv_shape *s = shape;
|
||||
while (s->count > 0) {
|
||||
keys[s->count - 1] = s->edge;
|
||||
s = s->parent;
|
||||
}
|
||||
|
||||
for (int i = 0; i < shape->count; i++) {
|
||||
if (!mrb_undef_p(siv->values[i])) {
|
||||
if ((*func)(mrb, keys[i], siv->values[i], p) != 0) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
shaped_iv_mark(mrb_state *mrb, struct RObject *obj)
|
||||
{
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)obj->iv;
|
||||
if (!siv) return 0;
|
||||
mrb_iv_shape *shape = siv->shape;
|
||||
for (int i = 0; i < shape->count; i++) {
|
||||
if (!mrb_undef_p(siv->values[i])) {
|
||||
mrb_gc_mark_value(mrb, siv->values[i]);
|
||||
}
|
||||
}
|
||||
return shape->count;
|
||||
}
|
||||
|
||||
static void
|
||||
shaped_iv_free(mrb_state *mrb, struct RObject *obj)
|
||||
{
|
||||
if (obj->iv) {
|
||||
mrb_free(mrb, obj->iv);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
shaped_iv_copy(mrb_state *mrb, struct RObject *dst, struct RObject *src)
|
||||
{
|
||||
mrb_shaped_iv *ssiv = (mrb_shaped_iv*)src->iv;
|
||||
if (!ssiv) {
|
||||
dst->iv = NULL;
|
||||
return;
|
||||
}
|
||||
mrb_iv_shape *shape = ssiv->shape;
|
||||
mrb_shaped_iv *dsiv = shaped_iv_alloc(mrb, shape);
|
||||
memcpy(dsiv->values, ssiv->values, sizeof(mrb_value) * shape->count);
|
||||
dst->iv = (iv_tbl*)dsiv;
|
||||
}
|
||||
|
||||
/* Public init/free for shape tree (called from state.c) */
|
||||
void
|
||||
mrb_init_shape(mrb_state *mrb)
|
||||
{
|
||||
mrb->root_shape = shape_root(mrb);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_free_shape(mrb_state *mrb)
|
||||
{
|
||||
if (mrb->root_shape) {
|
||||
shape_free_tree(mrb, mrb->root_shape);
|
||||
mrb->root_shape = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
iv_mark_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p)
|
||||
{
|
||||
@@ -270,6 +551,9 @@ mrb_gc_free_gv(mrb_state *mrb)
|
||||
size_t
|
||||
mrb_gc_mark_iv(mrb_state *mrb, struct RObject *obj)
|
||||
{
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
return shaped_iv_mark(mrb, obj);
|
||||
}
|
||||
mark_tbl(mrb, obj->iv);
|
||||
return iv_size(mrb, obj->iv);
|
||||
}
|
||||
@@ -277,6 +561,10 @@ mrb_gc_mark_iv(mrb_state *mrb, struct RObject *obj)
|
||||
void
|
||||
mrb_gc_free_iv(mrb_state *mrb, struct RObject *obj)
|
||||
{
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
shaped_iv_free(mrb, obj);
|
||||
return;
|
||||
}
|
||||
if (obj->iv) {
|
||||
iv_free(mrb, obj->iv);
|
||||
}
|
||||
@@ -331,8 +619,11 @@ class_iv_ptr(struct RClass *c)
|
||||
MRB_API mrb_value
|
||||
mrb_obj_iv_get(mrb_state *mrb, struct RObject *obj, mrb_sym sym)
|
||||
{
|
||||
mrb_value v;
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
return shaped_iv_get(obj, sym);
|
||||
}
|
||||
|
||||
mrb_value v;
|
||||
if (obj->iv && iv_get(mrb, obj->iv, sym, &v))
|
||||
return v;
|
||||
return mrb_nil_value();
|
||||
@@ -402,6 +693,11 @@ mrb_obj_iv_set_force(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value
|
||||
if (namespace_p(obj->tt)) {
|
||||
assign_class_name(mrb, obj, sym, v);
|
||||
}
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
shaped_iv_set(mrb, obj, sym, v);
|
||||
mrb_field_write_barrier_value(mrb, (struct RBasic*)obj, v);
|
||||
return;
|
||||
}
|
||||
if (!obj->iv) {
|
||||
obj->iv = iv_new(mrb);
|
||||
}
|
||||
@@ -447,6 +743,10 @@ MRB_API void
|
||||
mrb_iv_foreach(mrb_state *mrb, mrb_value obj, mrb_iv_foreach_func *func, void *p)
|
||||
{
|
||||
if (!obj_iv_p(obj)) return;
|
||||
if (MRB_OBJ_SHAPED_P(mrb_obj_ptr(obj))) {
|
||||
shaped_iv_foreach(mrb, mrb_obj_ptr(obj), func, p);
|
||||
return;
|
||||
}
|
||||
iv_foreach(mrb, mrb_obj_ptr(obj)->iv, func, p);
|
||||
}
|
||||
|
||||
@@ -492,9 +792,11 @@ mrb_iv_set(mrb_state *mrb, mrb_value obj, mrb_sym sym, mrb_value v)
|
||||
MRB_API mrb_bool
|
||||
mrb_obj_iv_defined(mrb_state *mrb, struct RObject *obj, mrb_sym sym)
|
||||
{
|
||||
iv_tbl *t;
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
return shaped_iv_defined(obj, sym);
|
||||
}
|
||||
|
||||
t = obj->iv;
|
||||
iv_tbl *t = obj->iv;
|
||||
if (t && iv_get(mrb, t, sym, NULL)) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
@@ -586,13 +888,55 @@ mrb_iv_copy(mrb_state *mrb, mrb_value dest, mrb_value src)
|
||||
struct RObject *d = mrb_obj_ptr(dest);
|
||||
struct RObject *s = mrb_obj_ptr(src);
|
||||
|
||||
if (d->iv) {
|
||||
iv_free(mrb, d->iv);
|
||||
d->iv = 0;
|
||||
/* free dest's existing IVs */
|
||||
if (MRB_OBJ_SHAPED_P(d)) {
|
||||
shaped_iv_free(mrb, d);
|
||||
d->iv = NULL;
|
||||
}
|
||||
if (s->iv) {
|
||||
mrb_write_barrier(mrb, (struct RBasic*)d);
|
||||
d->iv = iv_copy(mrb, s->iv);
|
||||
else if (d->iv) {
|
||||
iv_free(mrb, d->iv);
|
||||
d->iv = NULL;
|
||||
}
|
||||
|
||||
if (MRB_OBJ_SHAPED_P(s) && MRB_OBJ_SHAPED_P(d)) {
|
||||
/* both shaped: share shape, memcpy values */
|
||||
if (s->iv) {
|
||||
mrb_write_barrier(mrb, (struct RBasic*)d);
|
||||
shaped_iv_copy(mrb, d, s);
|
||||
}
|
||||
}
|
||||
else if (MRB_OBJ_SHAPED_P(s)) {
|
||||
/* src shaped, dest unshaped: convert src to iv_tbl copy */
|
||||
mrb_shaped_iv *ssiv = (mrb_shaped_iv*)s->iv;
|
||||
if (ssiv) {
|
||||
mrb_iv_shape *shape = ssiv->shape;
|
||||
if (shape->count > 0) {
|
||||
mrb_sym keys[MRB_SHAPE_MAX_IVS];
|
||||
mrb_iv_shape *sh = shape;
|
||||
while (sh->count > 0) {
|
||||
keys[sh->count - 1] = sh->edge;
|
||||
sh = sh->parent;
|
||||
}
|
||||
iv_tbl *t = iv_new(mrb);
|
||||
for (int i = 0; i < shape->count; i++) {
|
||||
if (!mrb_undef_p(ssiv->values[i])) {
|
||||
iv_put(mrb, t, keys[i], ssiv->values[i]);
|
||||
}
|
||||
}
|
||||
mrb_write_barrier(mrb, (struct RBasic*)d);
|
||||
d->iv = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* both unshaped or dest shaped but src unshaped */
|
||||
if (MRB_OBJ_SHAPED_P(d)) {
|
||||
d->flags &= ~MRB_FL_OBJ_SHAPED;
|
||||
}
|
||||
if (s->iv) {
|
||||
mrb_write_barrier(mrb, (struct RBasic*)d);
|
||||
d->iv = iv_copy(mrb, s->iv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,10 +958,25 @@ mrb_iv_remove(mrb_state *mrb, mrb_value obj, mrb_sym sym)
|
||||
{
|
||||
if (obj_iv_p(obj)) {
|
||||
struct RObject *o = mrb_obj_ptr(obj);
|
||||
mrb_check_frozen(mrb, o);
|
||||
|
||||
if (MRB_OBJ_SHAPED_P(o)) {
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)o->iv;
|
||||
if (siv) {
|
||||
int idx = shape_lookup(siv->shape, sym);
|
||||
if (idx >= 0 && !mrb_undef_p(siv->values[idx])) {
|
||||
mrb_value val = siv->values[idx];
|
||||
/* de-shape, then remove the key */
|
||||
shaped_to_iv_tbl(mrb, o);
|
||||
iv_del(mrb, o->iv, sym, NULL);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return mrb_undef_value();
|
||||
}
|
||||
|
||||
iv_tbl *t = o->iv;
|
||||
mrb_value val;
|
||||
|
||||
mrb_check_frozen(mrb, o);
|
||||
if (iv_del(mrb, t, sym, &val)) {
|
||||
return val;
|
||||
}
|
||||
@@ -661,7 +1020,13 @@ mrb_obj_instance_variables(mrb_state *mrb, mrb_value self)
|
||||
mrb_value ary = mrb_ary_new(mrb);
|
||||
|
||||
if (obj_iv_p(self)) {
|
||||
iv_foreach(mrb, mrb_obj_ptr(self)->iv, iv_i, &ary);
|
||||
struct RObject *obj = mrb_obj_ptr(self);
|
||||
if (MRB_OBJ_SHAPED_P(obj)) {
|
||||
shaped_iv_foreach(mrb, obj, iv_i, &ary);
|
||||
}
|
||||
else {
|
||||
iv_foreach(mrb, obj->iv, iv_i, &ary);
|
||||
}
|
||||
}
|
||||
return ary;
|
||||
}
|
||||
@@ -1458,7 +1823,14 @@ mrb_class_find_path(mrb_state *mrb, struct RClass *c)
|
||||
size_t
|
||||
mrb_obj_iv_tbl_memsize(mrb_value obj)
|
||||
{
|
||||
iv_tbl *t = mrb_obj_ptr(obj)->iv;
|
||||
struct RObject *o = mrb_obj_ptr(obj);
|
||||
if (MRB_OBJ_SHAPED_P(o)) {
|
||||
mrb_shaped_iv *siv = (mrb_shaped_iv*)o->iv;
|
||||
if (!siv) return 0;
|
||||
return offsetof(mrb_shaped_iv, values) +
|
||||
sizeof(mrb_value) * siv->shape->count;
|
||||
}
|
||||
iv_tbl *t = o->iv;
|
||||
if (t == NULL) return 0;
|
||||
return sizeof(iv_tbl) + t->alloc*(sizeof(mrb_value)+sizeof(mrb_sym));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user