Skip to content

Commit 000061f

Browse files
authored
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: #456
1 parent 0362c0a commit 000061f

File tree

1 file changed

+100
-2
lines changed

1 file changed

+100
-2
lines changed

quickjs.c

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18695,6 +18695,7 @@ typedef struct JSFunctionDef {
1869518695
JSAtom func_name; /* JS_ATOM_NULL if no name */
1869618696

1869718697
JSVarDef *vars;
18698+
uint32_t *vars_htab; // indexes into vars[]
1869818699
int var_size; /* allocated size for vars[] */
1869918700
int var_count;
1870018701
JSVarDef *args;
@@ -20517,6 +20518,86 @@ static __exception int emit_push_const(JSParseState *s, JSValue val,
2051720518
return 0;
2051820519
}
2051920520

20521+
// perl hash; variation of k&r hash with a different magic multiplier
20522+
// and a final shuffle to improve distribution of the low-order bits
20523+
static uint32_t hash_bytes(uint32_t h, const void *b, size_t n)
20524+
{
20525+
const char *p;
20526+
20527+
for (p = b; p < (char *)b + n; p++)
20528+
h = 33*h + *p;
20529+
h += h >> 5;
20530+
return h;
20531+
}
20532+
20533+
static uint32_t hash_atom(JSAtom atom)
20534+
{
20535+
return hash_bytes(0, &atom, sizeof(atom));
20536+
}
20537+
20538+
// caveat emptor: the table size must be a power of two in order for
20539+
// masking to work, and the load factor constant must be an odd number (5)
20540+
//
20541+
// f(n)=n+n/t is used to estimate the load factor but changing t to an
20542+
// even number introduces gaps in the output of f, sometimes "jumping"
20543+
// over the next power of two; it's at powers of two when the hash table
20544+
// must be resized
20545+
static int update_var_htab(JSContext *ctx, JSFunctionDef *fd)
20546+
{
20547+
uint32_t i, j, k, m, *p;
20548+
20549+
if (fd->var_count < 27) // 27 + 27/5 == 32
20550+
return 0;
20551+
k = fd->var_count - 1;
20552+
m = fd->var_count + fd->var_count/5;
20553+
if (m & (m - 1)) // unless power of two
20554+
goto insert;
20555+
m *= 2;
20556+
p = js_realloc(ctx, fd->vars_htab, m * sizeof(*fd->vars_htab));
20557+
if (!p)
20558+
return -1;
20559+
for (i = 0; i < m; i++)
20560+
p[i] = UINT32_MAX;
20561+
fd->vars_htab = p;
20562+
k = 0;
20563+
m--;
20564+
insert:
20565+
m = UINT32_MAX >> clz32(m);
20566+
do {
20567+
i = hash_atom(fd->vars[k].var_name);
20568+
j = 1;
20569+
for (;;) {
20570+
p = &fd->vars_htab[i & m];
20571+
if (*p == UINT32_MAX)
20572+
break;
20573+
i += j;
20574+
j += 1; // quadratic probing
20575+
}
20576+
*p = k++;
20577+
} while (k < (uint32_t)fd->var_count);
20578+
return 0;
20579+
}
20580+
20581+
static int find_var_htab(JSFunctionDef *fd, JSAtom var_name)
20582+
{
20583+
uint32_t i, j, m, *p;
20584+
20585+
i = hash_atom(var_name);
20586+
j = 1;
20587+
m = fd->var_count + fd->var_count/5;
20588+
m = UINT32_MAX >> clz32(m);
20589+
for (;;) {
20590+
p = &fd->vars_htab[i & m];
20591+
if (*p == UINT32_MAX)
20592+
return -1;
20593+
if (fd->vars[*p].var_name == var_name)
20594+
return *p;
20595+
i += j;
20596+
j += 1; // quadratic probing
20597+
}
20598+
return -1; // pacify compiler
20599+
}
20600+
2052020601
/* return the variable index or -1 if not found,
2052120602
add ARGUMENT_VAR_OFFSET for argument variables */
2052220603
static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
@@ -20531,11 +20612,24 @@ static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
2053120612

2053220613
static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
2053320614
{
20615+
JSVarDef *vd;
2053420616
int i;
20535-
for(i = fd->var_count; i-- > 0;) {
20536-
if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0)
20617+
20618+
if (fd->vars_htab) {
20619+
i = find_var_htab(fd, name);
20620+
if (i == -1)
20621+
goto not_found;
20622+
vd = &fd->vars[i];
20623+
if (fd->vars[i].scope_level == 0)
2053720624
return i;
2053820625
}
20626+
for(i = fd->var_count; i-- > 0;) {
20627+
vd = &fd->vars[i];
20628+
if (vd->var_name == name)
20629+
if (vd->scope_level == 0)
20630+
return i;
20631+
}
20632+
not_found:
2053920633
return find_arg(ctx, fd, name);
2054020634
}
2054120635

@@ -20710,6 +20804,8 @@ static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name)
2071020804
memset(vd, 0, sizeof(*vd));
2071120805
vd->var_name = JS_DupAtom(ctx, name);
2071220806
vd->func_pool_idx = -1;
20807+
if (update_var_htab(ctx, fd))
20808+
return -1;
2071320809
return fd->var_count - 1;
2071420810
}
2071520811

@@ -28410,6 +28506,7 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd)
2841028506
JS_FreeAtom(ctx, fd->vars[i].var_name);
2841128507
}
2841228508
js_free(ctx, fd->vars);
28509+
js_free(ctx, fd->vars_htab); // XXX can probably be freed earlier?
2841328510
for(i = 0; i < fd->arg_count; i++) {
2841428511
JS_FreeAtom(ctx, fd->args[i].var_name);
2841528512
}
@@ -32258,6 +32355,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
3225832355
b->defined_arg_count = fd->defined_arg_count;
3225932356
js_free(ctx, fd->args);
3226032357
js_free(ctx, fd->vars);
32358+
js_free(ctx, fd->vars_htab);
3226132359
}
3226232360
b->cpool_count = fd->cpool_count;
3226332361
if (b->cpool_count) {

0 commit comments

Comments
 (0)