mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
class.c: Switch mt_tbl to sorted array binary search for method lookups
By replacing the open‐addressing hash with a sorted array and binary search, we eliminate tombstone management and improve cache locality of method entries. This preserves the original contiguous values+keys layout and peak memory usage, while simplifying growth logic and delivering lookup performance gains.
This commit is contained in:
+114
-158
@@ -45,9 +45,9 @@ union mt_ptr {
|
||||
|
||||
/* method table structure */
|
||||
typedef struct mt_tbl {
|
||||
int size;
|
||||
int alloc;
|
||||
union mt_ptr *ptr;
|
||||
int size; /* # of used entries */
|
||||
int alloc; /* capacity */
|
||||
union mt_ptr *ptr; /* block: [ ptr[0...alloc] | keys[0...alloc] ] */
|
||||
} mt_tbl;
|
||||
|
||||
#ifdef MRB_USE_INLINE_METHOD_CACHE
|
||||
@@ -55,6 +55,37 @@ typedef struct mt_tbl {
|
||||
static uint8_t mt_cache[MT_INLINE_CACHE_SIZE];
|
||||
#endif
|
||||
|
||||
/* helper to get keys array */
|
||||
static inline mrb_sym*
|
||||
mt_keys(mt_tbl *t) {
|
||||
return (mrb_sym*)&t->ptr[t->alloc];
|
||||
}
|
||||
|
||||
static union mt_ptr*
|
||||
mt_vals(mt_tbl *t) {
|
||||
return t->ptr;
|
||||
}
|
||||
|
||||
/* allocate or grow the block to exactly alloc entries */
|
||||
static void
|
||||
mt_grow(mrb_state *mrb, mt_tbl *t, int new_alloc)
|
||||
{
|
||||
int old_alloc = t->alloc;
|
||||
size_t new_block = new_alloc * (sizeof(union mt_ptr) + sizeof(mrb_sym));
|
||||
|
||||
t->ptr = (union mt_ptr*)mrb_realloc(mrb, t->ptr, new_block);
|
||||
if (old_alloc > 0) {
|
||||
/* keys used to live at &ptr[old_alloc] */
|
||||
mrb_sym *old_keys = (mrb_sym*)&t->ptr[old_alloc];
|
||||
/* keys must now live at &ptr[new_alloc] */
|
||||
mrb_sym *new_keys = (mrb_sym*)&t->ptr[new_alloc];
|
||||
|
||||
/* move the old key array up to its new position */
|
||||
memmove(new_keys, old_keys, old_alloc * sizeof(mrb_sym));
|
||||
}
|
||||
t->alloc = new_alloc;
|
||||
}
|
||||
|
||||
/* Creates the method table. */
|
||||
static mt_tbl*
|
||||
mt_new(mrb_state *mrb)
|
||||
@@ -69,175 +100,107 @@ mt_new(mrb_state *mrb)
|
||||
return t;
|
||||
}
|
||||
|
||||
static void mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptr);
|
||||
|
||||
static void
|
||||
mt_rehash(mrb_state *mrb, mt_tbl *t)
|
||||
mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptrval)
|
||||
{
|
||||
int old_alloc = t->alloc;
|
||||
int new_alloc = old_alloc > 0 ? old_alloc << 1 : 8;
|
||||
union mt_ptr *old_ptr = t->ptr;
|
||||
|
||||
t->ptr = (union mt_ptr*)mrb_calloc(mrb, new_alloc, sizeof(union mt_ptr)+sizeof(mrb_sym));
|
||||
t->alloc = new_alloc;
|
||||
t->size = 0;
|
||||
if (old_alloc == 0) return;
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&old_ptr[old_alloc];
|
||||
union mt_ptr *vals = old_ptr;
|
||||
for (int i = 0; i < old_alloc; i++) {
|
||||
mrb_sym key = keys[i];
|
||||
if (MT_KEY_P(key)) {
|
||||
mt_put(mrb, t, MT_KEY_SYM(key), MT_KEY_FLG(key), vals[i]);
|
||||
}
|
||||
}
|
||||
mrb_free(mrb, old_ptr);
|
||||
}
|
||||
|
||||
#define slot_empty_p(slot) ((slot)->key == 0 && (slot)->func_p == 0)
|
||||
|
||||
/* Set the value for the symbol in the method table. */
|
||||
static void
|
||||
mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptr)
|
||||
{
|
||||
int pos, start, dpos = -1;
|
||||
mrb_sym key = MT_KEY(sym, flags);
|
||||
|
||||
/* ensure capacity */
|
||||
if (t->alloc == 0) {
|
||||
mt_rehash(mrb, t);
|
||||
mt_grow(mrb, t, 8);
|
||||
}
|
||||
else if (t->size == t->alloc) {
|
||||
mt_grow(mrb, t, t->alloc * 2);
|
||||
}
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
union mt_ptr *vals = t->ptr;
|
||||
int hash = mrb_int_hash_func(mrb, sym);
|
||||
start = pos = hash & (t->alloc-1);
|
||||
for (;;) {
|
||||
mrb_sym key = keys[pos];
|
||||
if (MT_KEY_SYM(key) == sym) {
|
||||
value_set:
|
||||
keys[pos] = MT_KEY(sym, flags);
|
||||
vals[pos] = ptr;
|
||||
return;
|
||||
}
|
||||
else if (key == MT_EMPTY) {
|
||||
t->size++;
|
||||
goto value_set;
|
||||
}
|
||||
else if (key == MT_DELETED && dpos < 0) {
|
||||
dpos = pos;
|
||||
}
|
||||
pos = (pos+1) & (t->alloc-1);
|
||||
if (pos == start) { /* not found */
|
||||
if (dpos > 0) { // In original code, this was dpos > 0, not dpos != -1
|
||||
t->size++;
|
||||
pos = dpos;
|
||||
goto value_set;
|
||||
}
|
||||
/* no room */
|
||||
mt_rehash(mrb, t);
|
||||
start = pos = hash & (t->alloc-1);
|
||||
keys = (mrb_sym*)&t->ptr[t->alloc]; // Need to re-assign keys
|
||||
vals = t->ptr; // Need to re-assign vals
|
||||
dpos = -1; // dpos should be reset as well
|
||||
}
|
||||
mrb_sym *keys = mt_keys(t);
|
||||
union mt_ptr *vals = mt_vals(t);
|
||||
|
||||
/* binary search [0..size) for sym */
|
||||
int lo = 0, hi = t->size;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi) >> 1;
|
||||
mrb_sym mid_sym = MT_KEY_SYM(keys[mid]);
|
||||
if (mid_sym < sym) lo = mid + 1;
|
||||
else hi = mid;
|
||||
}
|
||||
/* update if exists */
|
||||
if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) {
|
||||
keys[lo] = key;
|
||||
vals[lo] = ptrval;
|
||||
return;
|
||||
}
|
||||
/* insert at lo: shift right */
|
||||
if (t->size > lo) {
|
||||
int size = t->size - lo;
|
||||
memmove(&vals[lo+1], &vals[lo], size * sizeof(union mt_ptr));
|
||||
memmove(&keys[lo+1], &keys[lo], size * sizeof(mrb_sym));
|
||||
}
|
||||
keys[lo] = key;
|
||||
vals[lo] = ptrval;
|
||||
t->size++;
|
||||
}
|
||||
|
||||
/* Get a value for a symbol from the method table. */
|
||||
static mrb_sym
|
||||
mt_get(mrb_state *mrb, mt_tbl *t, mrb_sym sym, union mt_ptr *pp)
|
||||
{
|
||||
int pos;
|
||||
if (!t || t->size == 0) return 0;
|
||||
|
||||
if (t == NULL) return 0;
|
||||
if (t->alloc == 0) return 0;
|
||||
if (t->size == 0) return 0;
|
||||
mrb_sym *keys = mt_keys(t);
|
||||
union mt_ptr *vals = mt_vals(t);
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
union mt_ptr *vals = t->ptr;
|
||||
int hash = mrb_int_hash_func(mrb, sym);
|
||||
#ifdef MRB_USE_INLINE_METHOD_CACHE
|
||||
int cpos = (hash^(uintptr_t)t) % MT_INLINE_CACHE_SIZE;
|
||||
pos = mt_cache[cpos];
|
||||
if (pos < t->alloc) {
|
||||
mrb_sym key = keys[pos];
|
||||
if (key) {
|
||||
if (MT_KEY_SYM(key) == sym) {
|
||||
*pp = vals[pos];
|
||||
return key;
|
||||
}
|
||||
}
|
||||
int lo = 0, hi = t->size;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi) >> 1;
|
||||
mrb_sym mid_sym = MT_KEY_SYM(keys[mid]);
|
||||
if (mid_sym < sym) lo = mid + 1;
|
||||
else hi = mid;
|
||||
}
|
||||
#endif
|
||||
int start = pos = hash & (t->alloc-1);
|
||||
for (;;) {
|
||||
mrb_sym key = keys[pos];
|
||||
if (MT_KEY_SYM(key) == sym) {
|
||||
*pp = vals[pos];
|
||||
#ifdef MRB_USE_INLINE_METHOD_CACHE
|
||||
if (pos < 0x100) {
|
||||
mt_cache[cpos] = pos;
|
||||
}
|
||||
#endif
|
||||
return key;
|
||||
}
|
||||
else if (key == MT_EMPTY) {
|
||||
return 0;
|
||||
}
|
||||
pos = (pos+1) & (t->alloc-1);
|
||||
if (pos == start) { /* not found */
|
||||
return 0;
|
||||
}
|
||||
if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) {
|
||||
*pp = vals[lo];
|
||||
return keys[lo];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Deletes the value for the symbol from the method table. */
|
||||
static mrb_bool
|
||||
mt_del(mrb_state *mrb, mt_tbl *t, mrb_sym sym)
|
||||
{
|
||||
int pos, start;
|
||||
if (!t || t->size == 0) return FALSE;
|
||||
|
||||
if (t == NULL) return FALSE;
|
||||
if (t->alloc == 0) return FALSE;
|
||||
if (t->size == 0) return FALSE;
|
||||
mrb_sym *keys = mt_keys(t);
|
||||
union mt_ptr *vals = t->ptr;
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
int hash = mrb_int_hash_func(mrb, sym);
|
||||
start = pos = hash & (t->alloc-1);
|
||||
for (;;) {
|
||||
mrb_sym key = keys[pos];
|
||||
if (MT_KEY_SYM(key) == sym) {
|
||||
t->size--;
|
||||
keys[pos] = MT_DELETED;
|
||||
return TRUE;
|
||||
}
|
||||
else if (key == MT_EMPTY) {
|
||||
return FALSE;
|
||||
}
|
||||
pos = (pos+1) & (t->alloc-1);
|
||||
if (pos == start) { /* not found */
|
||||
return FALSE;
|
||||
}
|
||||
int lo = 0, hi = t->size;
|
||||
while (lo < hi) {
|
||||
int mid = (lo + hi) >> 1;
|
||||
mrb_sym mid_sym = MT_KEY_SYM(keys[mid]);
|
||||
if (mid_sym < sym) lo = mid + 1;
|
||||
else hi = mid;
|
||||
}
|
||||
if (lo < t->size && MT_KEY_SYM(keys[lo]) == sym) {
|
||||
/* shift left to remove entry */
|
||||
memmove(&vals[lo], &vals[lo+1], (t->size - lo - 1) * sizeof(union mt_ptr));
|
||||
memmove(&keys[lo], &keys[lo+1], (t->size - lo - 1) * sizeof(mrb_sym));
|
||||
t->size--;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Copy the method table. */
|
||||
static struct mt_tbl*
|
||||
static mt_tbl*
|
||||
mt_copy(mrb_state *mrb, mt_tbl *t)
|
||||
{
|
||||
mt_tbl *t2;
|
||||
|
||||
if (t == NULL) return NULL;
|
||||
if (t->alloc == 0) return NULL;
|
||||
if (t->size == 0) return NULL;
|
||||
|
||||
t2 = mt_new(mrb);
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
union mt_ptr *vals = t->ptr;
|
||||
for (int i=0; i<t->alloc; i++) {
|
||||
if (MT_KEY_P(keys[i])) {
|
||||
mt_put(mrb, t2, MT_KEY_SYM(keys[i]), MT_KEY_FLG(keys[i]), vals[i]);
|
||||
}
|
||||
}
|
||||
if (!t || t->size == 0) return NULL;
|
||||
mt_tbl *t2 = mt_new(mrb);
|
||||
mt_grow(mrb, t2, t->size);
|
||||
/* copy used entries */
|
||||
memcpy(mt_vals(t2), mt_vals(t), t->size * sizeof(union mt_ptr));
|
||||
memcpy(mt_keys(t2), mt_keys(t), t->size * sizeof(mrb_sym));
|
||||
t2->size = t->size;
|
||||
return t2;
|
||||
}
|
||||
|
||||
@@ -260,21 +223,16 @@ MRB_API void
|
||||
mrb_mt_foreach(mrb_state *mrb, struct RClass *c, mrb_mt_foreach_func *fn, void *p)
|
||||
{
|
||||
mt_tbl *t = c->mt;
|
||||
if (!t || t->size == 0) return;
|
||||
|
||||
if (t == NULL) return;
|
||||
if (t->alloc == 0) return;
|
||||
if (t->size == 0) return;
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
union mt_ptr *vals = t->ptr;
|
||||
for (int i=0; i<t->alloc; i++) {
|
||||
union mt_ptr *vals = mt_vals(t);
|
||||
mrb_sym *keys = mt_keys(t);
|
||||
for (int i=0; i<t->size; i++) {
|
||||
mrb_sym key = keys[i];
|
||||
if (MT_KEY_SYM(key)) {
|
||||
if (fn(mrb, MT_KEY_SYM(key), create_method_value(mrb, key, vals[i]), p) != 0)
|
||||
return;
|
||||
if (fn(mrb, MT_KEY_SYM(key), create_method_value(mrb, key, vals[i]), p) != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t
|
||||
@@ -282,13 +240,11 @@ mrb_gc_mark_mt(mrb_state *mrb, struct RClass *c)
|
||||
{
|
||||
mt_tbl *t = c->mt;
|
||||
|
||||
if (t == NULL) return 0;
|
||||
if (t->alloc == 0) return 0;
|
||||
if (t->size == 0) return 0;
|
||||
if (!t || t->size == 0) return 0;
|
||||
|
||||
mrb_sym *keys = (mrb_sym*)&t->ptr[t->alloc];
|
||||
union mt_ptr *vals = t->ptr;
|
||||
for (int i=0; i<t->alloc; i++) {
|
||||
mrb_sym *keys = mt_keys(t);
|
||||
union mt_ptr *vals = mt_vals(t);
|
||||
for (int i=0; i<t->size; i++) {
|
||||
if (MT_KEY_P(keys[i]) && (keys[i] & MT_FUNC) == 0) { /* Proc pointer */
|
||||
const struct RProc *p = vals[i].proc;
|
||||
mrb_gc_mark(mrb, (struct RBasic*)p);
|
||||
|
||||
Reference in New Issue
Block a user