Improve performance of variable resolver (#672)

Switch to a hash table when the number of variables grows beyond
a threshold.

Speeds up the test case from the linked issue by about 70%.

Fixes: https://github.com/quickjs-ng/quickjs/issues/456
This commit is contained in:
Ben Noordhuis 2024-11-12 21:22:47 +01:00 committed by GitHub
parent 0362c0a4eb
commit 000061f635
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

102
quickjs.c
View file

@ -18695,6 +18695,7 @@ typedef struct JSFunctionDef {
JSAtom func_name; /* JS_ATOM_NULL if no name */ JSAtom func_name; /* JS_ATOM_NULL if no name */
JSVarDef *vars; JSVarDef *vars;
uint32_t *vars_htab; // indexes into vars[]
int var_size; /* allocated size for vars[] */ int var_size; /* allocated size for vars[] */
int var_count; int var_count;
JSVarDef *args; JSVarDef *args;
@ -20517,6 +20518,86 @@ static __exception int emit_push_const(JSParseState *s, JSValue val,
return 0; return 0;
} }
// perl hash; variation of k&r hash with a different magic multiplier
// and a final shuffle to improve distribution of the low-order bits
static uint32_t hash_bytes(uint32_t h, const void *b, size_t n)
{
const char *p;
for (p = b; p < (char *)b + n; p++)
h = 33*h + *p;
h += h >> 5;
return h;
}
static uint32_t hash_atom(JSAtom atom)
{
return hash_bytes(0, &atom, sizeof(atom));
}
// caveat emptor: the table size must be a power of two in order for
// masking to work, and the load factor constant must be an odd number (5)
//
// f(n)=n+n/t is used to estimate the load factor but changing t to an
// even number introduces gaps in the output of f, sometimes "jumping"
// over the next power of two; it's at powers of two when the hash table
// must be resized
static int update_var_htab(JSContext *ctx, JSFunctionDef *fd)
{
uint32_t i, j, k, m, *p;
if (fd->var_count < 27) // 27 + 27/5 == 32
return 0;
k = fd->var_count - 1;
m = fd->var_count + fd->var_count/5;
if (m & (m - 1)) // unless power of two
goto insert;
m *= 2;
p = js_realloc(ctx, fd->vars_htab, m * sizeof(*fd->vars_htab));
if (!p)
return -1;
for (i = 0; i < m; i++)
p[i] = UINT32_MAX;
fd->vars_htab = p;
k = 0;
m--;
insert:
m = UINT32_MAX >> clz32(m);
do {
i = hash_atom(fd->vars[k].var_name);
j = 1;
for (;;) {
p = &fd->vars_htab[i & m];
if (*p == UINT32_MAX)
break;
i += j;
j += 1; // quadratic probing
}
*p = k++;
} while (k < (uint32_t)fd->var_count);
return 0;
}
static int find_var_htab(JSFunctionDef *fd, JSAtom var_name)
{
uint32_t i, j, m, *p;
i = hash_atom(var_name);
j = 1;
m = fd->var_count + fd->var_count/5;
m = UINT32_MAX >> clz32(m);
for (;;) {
p = &fd->vars_htab[i & m];
if (*p == UINT32_MAX)
return -1;
if (fd->vars[*p].var_name == var_name)
return *p;
i += j;
j += 1; // quadratic probing
}
return -1; // pacify compiler
}
/* return the variable index or -1 if not found, /* return the variable index or -1 if not found,
add ARGUMENT_VAR_OFFSET for argument variables */ add ARGUMENT_VAR_OFFSET for argument variables */
static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
@ -20531,11 +20612,24 @@ static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
{ {
JSVarDef *vd;
int i; int i;
for(i = fd->var_count; i-- > 0;) {
if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0) if (fd->vars_htab) {
i = find_var_htab(fd, name);
if (i == -1)
goto not_found;
vd = &fd->vars[i];
if (fd->vars[i].scope_level == 0)
return i; return i;
} }
for(i = fd->var_count; i-- > 0;) {
vd = &fd->vars[i];
if (vd->var_name == name)
if (vd->scope_level == 0)
return i;
}
not_found:
return find_arg(ctx, fd, name); return find_arg(ctx, fd, name);
} }
@ -20710,6 +20804,8 @@ static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
memset(vd, 0, sizeof(*vd)); memset(vd, 0, sizeof(*vd));
vd->var_name = JS_DupAtom(ctx, name); vd->var_name = JS_DupAtom(ctx, name);
vd->func_pool_idx = -1; vd->func_pool_idx = -1;
if (update_var_htab(ctx, fd))
return -1;
return fd->var_count - 1; return fd->var_count - 1;
} }
@ -28410,6 +28506,7 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
JS_FreeAtom(ctx, fd->vars[i].var_name); JS_FreeAtom(ctx, fd->vars[i].var_name);
} }
js_free(ctx, fd->vars); js_free(ctx, fd->vars);
js_free(ctx, fd->vars_htab); // XXX can probably be freed earlier?
for(i = 0; i < fd->arg_count; i++) { for(i = 0; i < fd->arg_count; i++) {
JS_FreeAtom(ctx, fd->args[i].var_name); JS_FreeAtom(ctx, fd->args[i].var_name);
} }
@ -32258,6 +32355,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
b->defined_arg_count = fd->defined_arg_count; b->defined_arg_count = fd->defined_arg_count;
js_free(ctx, fd->args); js_free(ctx, fd->args);
js_free(ctx, fd->vars); js_free(ctx, fd->vars);
js_free(ctx, fd->vars_htab);
} }
b->cpool_count = fd->cpool_count; b->cpool_count = fd->cpool_count;
if (b->cpool_count) { if (b->cpool_count) {