diff --git a/config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml b/config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml index 008b836180..bea59096e3 100644 --- a/config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml +++ b/config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml @@ -1,6 +1,6 @@ define: DUK_USE_HOBJECT_HASH_PROP_LIMIT introduced: 2.1.0 -default: 8 +default: 64 tags: - performance - lowmemory @@ -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. diff --git a/src-input/duk_forwdecl.h b/src-input/duk_forwdecl.h index b001b3c5b8..f97d7c51aa 100644 --- a/src-input/duk_forwdecl.h +++ b/src-input/duk_forwdecl.h @@ -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; @@ -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; diff --git a/src-input/duk_heap.h b/src-input/duk_heap.h index 0eb718df17..1ad5ed08a9 100644 --- a/src-input/duk_heap.h +++ b/src-input/duk_heap.h @@ -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 */ @@ -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. @@ -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. */ @@ -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); diff --git a/src-input/duk_heap_alloc.c b/src-input/duk_heap_alloc.c index 0a17ca8872..342d6c091a 100644 --- a/src-input/duk_heap_alloc.c +++ b/src-input/duk_heap_alloc.c @@ -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); @@ -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 diff --git a/src-input/duk_heap_slotcache.c b/src-input/duk_heap_slotcache.c new file mode 100644 index 0000000000..fd50a6fe2d --- /dev/null +++ b/src-input/duk_heap_slotcache.c @@ -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; +} diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 09f911281d..f1d0d8f8c4 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -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); @@ -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; diff --git a/tools/configure.py b/tools/configure.py index f3b0464baf..187fd7409b 100644 --- a/tools/configure.py +++ b/tools/configure.py @@ -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', diff --git a/util/dist.py b/util/dist.py index 7e9e4981da..99d1f8ac18 100644 --- a/util/dist.py +++ b/util/dist.py @@ -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',