Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a best effort heap-wide object property slot cache #1289

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
define: DUK_USE_HOBJECT_HASH_PROP_LIMIT
introduced: 2.1.0
default: 8
default: 64
tags:
- performance
- lowmemory
Expand All @@ -18,3 +18,11 @@ description: >

A lower value improves performance (a value as low a 4-8 can be useful)
while a higher value conserves memory.

The heap-wide object property slot cache accelerates repeated property
accesses to small and medium size objects with no upfront cost, so the
default limit has been raised to a relatively high value; the hash part
still makes sense for large objects where slot cache misses lead to very
large linear scans. The hash part also makes sense for long term objects
which are accessed often, e.g. prototype objects, but these's currently not
enough state to ensure they get and keep a hash part.
2 changes: 2 additions & 0 deletions src-input/duk_forwdecl.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct duk_breakpoint;
struct duk_activation;
struct duk_catcher;
struct duk_strcache;
struct duk_slotcache_entry;
struct duk_ljstate;
struct duk_strtab_entry;

Expand Down Expand Up @@ -105,6 +106,7 @@ typedef struct duk_breakpoint duk_breakpoint;
typedef struct duk_activation duk_activation;
typedef struct duk_catcher duk_catcher;
typedef struct duk_strcache duk_strcache;
typedef struct duk_slotcache_entry duk_slotcache_entry;
typedef struct duk_ljstate duk_ljstate;
typedef struct duk_strtab_entry duk_strtab_entry;

Expand Down
24 changes: 24 additions & 0 deletions src-input/duk_heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@
#define DUK_HEAP_REMOVE_FROM_FINALIZE_LIST(heap,hdr) duk_heap_remove_from_finalize_list((heap), (hdr))
#endif

/* Slotcache is used to speeding up object property lookups without
* caching a property value.
*/
#define DUK_HEAP_SLOTCACHE_SIZE 256

/*
* Built-in strings
*/
Expand Down Expand Up @@ -285,6 +290,15 @@ struct duk_strcache {
duk_uint32_t cidx;
};

/*
* Property slot cache. Best effort data structure to cache property
* table slots for recently accessed object/key combinations.
*/

struct duk_slotcache_entry {
duk_uint8_t slot;
};

/*
* Longjmp state, contains the information needed to perform a longjmp.
* Longjmp related values are written to value1, value2, and iserror.
Expand Down Expand Up @@ -535,6 +549,11 @@ struct duk_heap {
*/
duk_strcache strcache[DUK_HEAP_STRCACHE_SIZE];

/* Property slot cache, global structure for speeding up commonly
* referenced property access.
*/
duk_slotcache_entry slotcache[DUK_HEAP_SLOTCACHE_SIZE];

/* Built-in strings. */
#if defined(DUK_USE_ROM_STRINGS)
/* No field needed when strings are in ROM. */
Expand Down Expand Up @@ -595,6 +614,11 @@ DUK_INTERNAL void duk_heap_strtable_dump(duk_heap *heap);
DUK_INTERNAL_DECL void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h);
DUK_INTERNAL_DECL duk_uint_fast32_t duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_uint_fast32_t char_offset);

/* FIXME: duk_small_uint_t? */
DUK_INTERNAL_DECL duk_uint32_t duk_heap_slotcache_lookup(duk_heap *heap, duk_hobject *obj, duk_hstring *key);
DUK_INTERNAL_DECL void duk_heap_slotcache_insert(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_uint32_t slot);
DUK_INTERNAL_DECL duk_uint32_t duk_heap_slotcache_getkey(duk_hobject *obj, duk_hstring *key);

#if defined(DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS)
DUK_INTERNAL_DECL void *duk_default_alloc_function(void *udata, duk_size_t size);
DUK_INTERNAL_DECL void *duk_default_realloc_function(void *udata, void *ptr, duk_size_t newsize);
Expand Down
7 changes: 7 additions & 0 deletions src-input/duk_heap_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ DUK_LOCAL void duk__dump_type_sizes(void) {
DUK__DUMPSZ(duk_activation);
DUK__DUMPSZ(duk_catcher);
DUK__DUMPSZ(duk_strcache);
DUK__DUMPSZ(duk_slotcache_entry);
DUK__DUMPSZ(duk_ljstate);
DUK__DUMPSZ(duk_fixedbuffer);
DUK__DUMPSZ(duk_bitdecoder_ctx);
Expand Down Expand Up @@ -1051,6 +1052,12 @@ duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
}
#endif

/*
* Init slotcache
*/

/* Nothing to init now. */

/* XXX: error handling is incomplete. It would be cleanest if
* there was a setjmp catchpoint, so that all init code could
* freely throw errors. If that were the case, the return code
Expand Down
52 changes: 52 additions & 0 deletions src-input/duk_heap_slotcache.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Object property table slot cache.
*/

#include "duk_internal.h"

/* FIXME: check if it's useful to compute the slotcache key only once in property code
* (live range is not trivial so maybe not).
*/

DUK_LOCAL DUK_ALWAYS_INLINE duk_uint32_t duk__slotcache_compute_key(duk_hobject *obj, duk_hstring *key) {
DUK_ASSERT(obj != NULL);
DUK_ASSERT(key != NULL);

return (((duk_uint32_t) obj) ^ DUK_HSTRING_GET_HASH(key)) & (DUK_HEAP_SLOTCACHE_SIZE - 1);
}

DUK_INTERNAL duk_uint32_t duk_heap_slotcache_getkey(duk_hobject *obj, duk_hstring *key) {
DUK_ASSERT(obj != NULL);
DUK_ASSERT(key != NULL);

return duk__slotcache_compute_key(obj, key);
}

DUK_INTERNAL duk_uint32_t duk_heap_slotcache_lookup(duk_heap *heap, duk_hobject *obj, duk_hstring *key) {
duk_uint32_t idx;
duk_slotcache_entry *ent;

DUK_ASSERT(heap != NULL);
DUK_ASSERT(obj != NULL);
DUK_ASSERT(key != NULL);

/* FIXME: assert: hashlimit <= 256 */

idx = duk__slotcache_compute_key(obj, key);
ent = heap->slotcache + idx;
return ent->slot;
}

DUK_INTERNAL void duk_heap_slotcache_insert(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_uint32_t slot) {
duk_uint32_t idx;
duk_slotcache_entry *ent;

/* FIXME: assert: hashlimit <= 256 */
/* FIXME: skip check if hash part present and hashlimit correct */
if (DUK_UNLIKELY(slot >= DUK_UINT8_MAX)) {
return;
}
idx = duk__slotcache_compute_key(obj, key);
ent = heap->slotcache + idx;
ent->slot = (duk_uint16_t) slot;
}
18 changes: 18 additions & 0 deletions src-input/duk_hobject_props.c
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,8 @@ DUK_INTERNAL void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj)
*/

DUK_INTERNAL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx) {
duk_uint32_t slot;

DUK_ASSERT(obj != NULL);
DUK_ASSERT(key != NULL);
DUK_ASSERT(e_idx != NULL);
Expand All @@ -1143,10 +1145,26 @@ DUK_INTERNAL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *o
duk_hstring **h_keys_base;
DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup"));

slot = duk_heap_slotcache_lookup(heap, obj, key);
if (slot < DUK_HOBJECT_GET_ENEXT(obj)) {
if (DUK_HOBJECT_E_GET_KEY(heap, obj, slot) == key) {
DUK_D(DUK_DPRINT("slot cache hit: %p %p %!O -> %ld (slotcache lookup key %lu)",
(void *) obj, (void *) key, key, (long) slot,
(unsigned long) duk_heap_slotcache_getkey(obj, key)));
*e_idx = slot;
*h_idx = -1;
return;
}
}

h_keys_base = DUK_HOBJECT_E_GET_KEY_BASE(heap, obj);
n = DUK_HOBJECT_GET_ENEXT(obj);
for (i = 0; i < n; i++) {
if (h_keys_base[i] == key) {
DUK_D(DUK_DPRINT("slot cache insert: %p %p %!O -> %ld (slotcache lookup key %lu)",
(void *) obj, (void *) key, key, (long) i,
(unsigned long) duk_heap_slotcache_getkey(obj, key)));
duk_heap_slotcache_insert(heap, obj, key, i);
*e_idx = i;
*h_idx = -1;
return;
Expand Down
1 change: 1 addition & 0 deletions tools/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ def default_from_script_path(optname, orig, alternatives):
'duk_heap_refcount.c',
'duk_heap_stringcache.c',
'duk_heap_stringtable.c',
'duk_heap_slotcache.c',
'duk_hnatfunc.h',
'duk_hobject_alloc.c',
'duk_hobject_class.c',
Expand Down
1 change: 1 addition & 0 deletions util/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def main():
'duk_heap_refcount.c',
'duk_heap_stringcache.c',
'duk_heap_stringtable.c',
'duk_heap_slotcache.c',
'duk_henv.h',
'duk_hnatfunc.h',
'duk_hobject_alloc.c',
Expand Down