reduced memory usage of Map hash table

This commit is contained in:
Fabrice Bellard 2025-03-22 11:28:23 +01:00
parent 49413985eb
commit d1bb520f29

View File

@ -45960,7 +45960,7 @@ typedef struct JSMapRecord {
struct JSMapState *map; struct JSMapState *map;
struct JSMapRecord *next_weak_ref; struct JSMapRecord *next_weak_ref;
struct list_head link; struct list_head link;
struct list_head hash_link; struct JSMapRecord *hash_next;
JSValue key; JSValue key;
JSValue value; JSValue value;
} JSMapRecord; } JSMapRecord;
@ -45969,7 +45969,7 @@ typedef struct JSMapState {
BOOL is_weak; /* TRUE if WeakSet/WeakMap */ BOOL is_weak; /* TRUE if WeakSet/WeakMap */
struct list_head records; /* list of JSMapRecord.link */ struct list_head records; /* list of JSMapRecord.link */
uint32_t record_count; uint32_t record_count;
struct list_head *hash_table; JSMapRecord **hash_table;
uint32_t hash_size; /* must be a power of two */ uint32_t hash_size; /* must be a power of two */
uint32_t record_count_threshold; /* count at which a hash table uint32_t record_count_threshold; /* count at which a hash table
resize is needed */ resize is needed */
@ -45998,10 +45998,9 @@ static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target,
s->is_weak = is_weak; s->is_weak = is_weak;
JS_SetOpaque(obj, s); JS_SetOpaque(obj, s);
s->hash_size = 1; s->hash_size = 1;
s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size); s->hash_table = js_mallocz(ctx, sizeof(s->hash_table[0]) * s->hash_size);
if (!s->hash_table) if (!s->hash_table)
goto fail; goto fail;
init_list_head(&s->hash_table[0]);
s->record_count_threshold = 4; s->record_count_threshold = 4;
arr = JS_UNDEFINED; arr = JS_UNDEFINED;
@ -46100,7 +46099,7 @@ static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key)
} }
/* XXX: better hash ? */ /* XXX: better hash ? */
static uint32_t map_hash_key(JSContext *ctx, JSValueConst key) static uint32_t map_hash_key(JSValueConst key)
{ {
uint32_t tag = JS_VALUE_GET_NORM_TAG(key); uint32_t tag = JS_VALUE_GET_NORM_TAG(key);
uint32_t h; uint32_t h;
@ -46158,12 +46157,10 @@ static uint32_t map_hash_key(JSContext *ctx, JSValueConst key)
static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
JSValueConst key) JSValueConst key)
{ {
struct list_head *el;
JSMapRecord *mr; JSMapRecord *mr;
uint32_t h; uint32_t h;
h = map_hash_key(ctx, key) & (s->hash_size - 1); h = map_hash_key(key) & (s->hash_size - 1);
list_for_each(el, &s->hash_table[h]) { for(mr = s->hash_table[h]; mr != NULL; mr = mr->hash_next) {
mr = list_entry(el, JSMapRecord, hash_link);
if (js_same_value_zero(ctx, mr->key, key)) if (js_same_value_zero(ctx, mr->key, key))
return mr; return mr;
} }
@ -46172,10 +46169,10 @@ static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
static void map_hash_resize(JSContext *ctx, JSMapState *s) static void map_hash_resize(JSContext *ctx, JSMapState *s)
{ {
uint32_t new_hash_size, i, h; uint32_t new_hash_size, h;
size_t slack; size_t slack;
struct list_head *new_hash_table, *el; struct list_head *el;
JSMapRecord *mr; JSMapRecord *mr, **new_hash_table;
/* XXX: no reporting of memory allocation failure */ /* XXX: no reporting of memory allocation failure */
if (s->hash_size == 1) if (s->hash_size == 1)
@ -46187,14 +46184,14 @@ static void map_hash_resize(JSContext *ctx, JSMapState *s)
if (!new_hash_table) if (!new_hash_table)
return; return;
for(i = 0; i < new_hash_size; i++) memset(new_hash_table, 0, sizeof(new_hash_table[0]) * new_hash_size);
init_list_head(&new_hash_table[i]);
list_for_each(el, &s->records) { list_for_each(el, &s->records) {
mr = list_entry(el, JSMapRecord, link); mr = list_entry(el, JSMapRecord, link);
if (!mr->empty) { if (!mr->empty) {
h = map_hash_key(ctx, mr->key) & (new_hash_size - 1); h = map_hash_key(mr->key) & (new_hash_size - 1);
list_add_tail(&mr->hash_link, &new_hash_table[h]); mr->hash_next = new_hash_table[h];
new_hash_table[h] = mr;
} }
} }
s->hash_table = new_hash_table; s->hash_table = new_hash_table;
@ -46223,8 +46220,9 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
JS_DupValue(ctx, key); JS_DupValue(ctx, key);
} }
mr->key = (JSValue)key; mr->key = (JSValue)key;
h = map_hash_key(ctx, key) & (s->hash_size - 1); h = map_hash_key(key) & (s->hash_size - 1);
list_add_tail(&mr->hash_link, &s->hash_table[h]); mr->hash_next = s->hash_table[h];
s->hash_table[h] = mr;
list_add_tail(&mr->link, &s->records); list_add_tail(&mr->link, &s->records);
s->record_count++; s->record_count++;
if (s->record_count >= s->record_count_threshold) { if (s->record_count >= s->record_count_threshold) {
@ -46254,11 +46252,12 @@ static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
*pmr = mr1->next_weak_ref; *pmr = mr1->next_weak_ref;
} }
/* warning: the record must be removed from the hash table before */
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
{ {
if (mr->empty) if (mr->empty)
return; return;
list_del(&mr->hash_link);
if (s->is_weak) { if (s->is_weak) {
delete_weak_ref(rt, mr); delete_weak_ref(rt, mr);
} else { } else {
@ -46289,16 +46288,31 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
static void reset_weak_ref(JSRuntime *rt, JSObject *p) static void reset_weak_ref(JSRuntime *rt, JSObject *p)
{ {
JSMapRecord *mr, *mr_next; JSMapRecord *mr, *mr_next, **pmr, *mr1;
JSMapState *s; JSMapState *s;
uint32_t h;
JSValue key;
/* first pass to remove the records from the WeakMap/WeakSet /* first pass to remove the records from the WeakMap/WeakSet
lists */ lists */
key = JS_MKPTR(JS_TAG_OBJECT, p);
for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) { for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
s = mr->map; s = mr->map;
assert(s->is_weak); assert(s->is_weak);
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
list_del(&mr->hash_link);
/* remove the record from hash table */
h = map_hash_key(key) & (s->hash_size - 1);
pmr = &s->hash_table[h];
for(;;) {
mr1 = *pmr;
assert(mr1 != NULL);
if (mr1 == mr)
break;
pmr = &mr1->hash_next;
}
*pmr = mr->hash_next;
list_del(&mr->link); list_del(&mr->link);
} }
@ -46376,15 +46390,28 @@ static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic) int argc, JSValueConst *argv, int magic)
{ {
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
JSMapRecord *mr; JSMapRecord *mr, **pmr;
JSValueConst key; JSValueConst key;
uint32_t h;
if (!s) if (!s)
return JS_EXCEPTION; return JS_EXCEPTION;
key = map_normalize_key(ctx, argv[0]); key = map_normalize_key(ctx, argv[0]);
mr = map_find_record(ctx, s, key);
if (!mr) h = map_hash_key(key) & (s->hash_size - 1);
pmr = &s->hash_table[h];
for(;;) {
mr = *pmr;
if (mr == NULL)
return JS_FALSE; return JS_FALSE;
if (js_same_value_zero(ctx, mr->key, key))
break;
pmr = &mr->hash_next;
}
/* remove from the hash table */
*pmr = mr->hash_next;
map_delete_record(ctx->rt, s, mr); map_delete_record(ctx->rt, s, mr);
return JS_TRUE; return JS_TRUE;
} }
@ -46398,6 +46425,10 @@ static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val,
if (!s) if (!s)
return JS_EXCEPTION; return JS_EXCEPTION;
/* remove from the hash table */
memset(s->hash_table, 0, sizeof(s->hash_table[0]) * s->hash_size);
list_for_each_safe(el, el1, &s->records) { list_for_each_safe(el, el1, &s->records) {
mr = list_entry(el, JSMapRecord, link); mr = list_entry(el, JSMapRecord, link);
map_delete_record(ctx->rt, s, mr); map_delete_record(ctx->rt, s, mr);