From 8a91c91a69387c487225f01f69ddb2cba8f969c3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 22:50:29 +0000 Subject: [PATCH] Improve method table performance by rehashing at 75% load factor. The method table (mt_tbl) in src/class.c previously only rehashed when it became completely full. With linear probing, this could lead to significant performance degradation for lookups and insertions as the table approached full capacity. This change introduces a load factor (MT_LOAD_FACTOR_NUM/MT_LOAD_FACTOR_DEN, set to 3/4 or 0.75). The mt_put function now checks if adding a new element would cause the table's size to meet or exceed this load factor relative to its allocated capacity. If so, it triggers a rehash before inserting the new element. This helps maintain more empty slots in the hash table, improving the average-case performance of linear probing and reducing the likelihood of worst-case scenarios. The existing initial allocation size and doubling strategy for rehashing are retained. --- src/class.c | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/class.c b/src/class.c index ba413dfb1..75682225b 100644 --- a/src/class.c +++ b/src/class.c @@ -16,6 +16,9 @@ #include #include #include + +#define MT_LOAD_FACTOR_NUM 3 // Numerator for load factor (represents 0.75) +#define MT_LOAD_FACTOR_DEN 4 // Denominator for load factor #include #include @@ -102,41 +105,55 @@ mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptr) { int pos, start, dpos = -1; - if (t->alloc == 0) { + // Rehash if t->alloc is 0 (initial allocation) + // or if (t->size + 1) / t->alloc would meet or exceed the load factor. + // This check helps maintain performance by keeping the table from getting too full. + if (t->alloc == 0 || ((t->size + 1) * MT_LOAD_FACTOR_DEN >= t->alloc * MT_LOAD_FACTOR_NUM)) { mt_rehash(mrb, t); } + // These assignments MUST come AFTER any potential mt_rehash call, + // as mt_rehash changes t->ptr and t->alloc. 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); + start = pos = hash & (t->alloc-1); // t->alloc is always a power of 2 after mt_rehash + for (;;) { mrb_sym key = keys[pos]; - if (MT_KEY_SYM(key) == sym) { - value_set: + if (MT_KEY_SYM(key) == sym) { // Key found, update existing method + value_set: // Label to jump to for setting the value keys[pos] = MT_KEY(sym, flags); vals[pos] = ptr; return; } - else if (key == MT_EMPTY) { - t->size++; + else if (key == MT_EMPTY) { // Empty slot found + if (dpos != -1) { // If we passed a deleted slot, use that one instead + pos = dpos; + } + t->size++; // A new element is being added, so increment size. goto value_set; } - else if (key == MT_DELETED && dpos < 0) { - dpos = pos; + else if (key == MT_DELETED) { // Deleted slot found + if (dpos == -1) { // Store the first deleted slot found + dpos = pos; + } } - pos = (pos+1) & (t->alloc-1); - if (pos == start) { /* not found */ - if (dpos > 0) { - t->size++; + pos = (pos+1) & (t->alloc-1); // Move to the next slot (linear probing) + if (pos == start) { // Cycled through all slots + if (dpos != -1) { // If we found a deleted slot during the cycle, use it pos = dpos; + t->size++; // We are using a deleted slot for a new entry. goto value_set; } - /* no room */ + // No empty or deleted slot found in the probe sequence. The table is full. + // This rehash is a fallback. The load factor check should ideally prevent this. mt_rehash(mrb, t); - start = pos = hash & (t->alloc-1); + // Recalculate pointers and hash position after rehash keys = (mrb_sym*)&t->ptr[t->alloc]; vals = t->ptr; + start = pos = hash & (t->alloc-1); + dpos = -1; // Reset dpos as the table structure changed } } }