From 5343e4498be24920cf8d7754d3a1cbfce882470a Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sun, 15 Jan 2017 04:30:34 +0200 Subject: [PATCH 1/4] Config hash prop limit to 64 --- .../DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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. From 55c45c684edabacc521ac86172e310decac4c9f7 Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sat, 14 Jan 2017 23:44:52 +0200 Subject: [PATCH 2/4] Add property slotcache --- src-input/duk_forwdecl.h | 2 ++ src-input/duk_heap.h | 22 ++++++++++++++++++++++ src-input/duk_heap_alloc.c | 7 +++++++ src-input/duk_heap_slotcache.c | 34 ++++++++++++++++++++++++++++++++++ src-input/duk_hobject_props.c | 14 ++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 src-input/duk_heap_slotcache.c 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..9bbe86a304 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 8192 + /* * 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,9 @@ 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); +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); + #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..6390949238 --- /dev/null +++ b/src-input/duk_heap_slotcache.c @@ -0,0 +1,34 @@ +/* + * Object property table slot cache. + */ + +#include "duk_internal.h" + +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_uint32_t) obj) ^ DUK_HSTRING_GET_HASH(key); + ent = heap->slotcache + (idx & (DUK_HEAP_SLOTCACHE_SIZE - 1)); + 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_UINT16_MAX)) { + return; + } + idx = ((duk_uint32_t) obj) ^ DUK_HSTRING_GET_HASH(key); + ent = heap->slotcache + (idx & (DUK_HEAP_SLOTCACHE_SIZE - 1)); + ent->slot = (duk_uint16_t) slot; +} diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 09f911281d..75a53c3e19 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,22 @@ 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 -> %ld", (void *) obj, (void *) key, (long) slot)); + *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 -> %ld", (void *) obj, (void *) key, (long) i)); + duk_heap_slotcache_insert(heap, obj, key, i); *e_idx = i; *h_idx = -1; return; From 6d1f7e91d4d21696a2c019d1072843f64234f42e Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sat, 14 Jan 2017 23:45:05 +0200 Subject: [PATCH 3/4] Add slotcache to dist/configure --- tools/configure.py | 1 + util/dist.py | 1 + 2 files changed, 2 insertions(+) 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', From 6bcc8866351e8e269799ef5ddc9547c808ab52d1 Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sun, 15 Jan 2017 18:21:38 +0200 Subject: [PATCH 4/4] Work in progress --- src-input/duk_heap.h | 4 +++- src-input/duk_heap_slotcache.c | 28 +++++++++++++++++++++++----- src-input/duk_hobject_props.c | 8 ++++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src-input/duk_heap.h b/src-input/duk_heap.h index 9bbe86a304..1ad5ed08a9 100644 --- a/src-input/duk_heap.h +++ b/src-input/duk_heap.h @@ -149,7 +149,7 @@ /* Slotcache is used to speeding up object property lookups without * caching a property value. */ -#define DUK_HEAP_SLOTCACHE_SIZE 8192 +#define DUK_HEAP_SLOTCACHE_SIZE 256 /* * Built-in strings @@ -614,8 +614,10 @@ 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); diff --git a/src-input/duk_heap_slotcache.c b/src-input/duk_heap_slotcache.c index 6390949238..fd50a6fe2d 100644 --- a/src-input/duk_heap_slotcache.c +++ b/src-input/duk_heap_slotcache.c @@ -4,6 +4,24 @@ #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; @@ -14,8 +32,8 @@ DUK_INTERNAL duk_uint32_t duk_heap_slotcache_lookup(duk_heap *heap, duk_hobject /* FIXME: assert: hashlimit <= 256 */ - idx = ((duk_uint32_t) obj) ^ DUK_HSTRING_GET_HASH(key); - ent = heap->slotcache + (idx & (DUK_HEAP_SLOTCACHE_SIZE - 1)); + idx = duk__slotcache_compute_key(obj, key); + ent = heap->slotcache + idx; return ent->slot; } @@ -25,10 +43,10 @@ DUK_INTERNAL void duk_heap_slotcache_insert(duk_heap *heap, duk_hobject *obj, du /* FIXME: assert: hashlimit <= 256 */ /* FIXME: skip check if hash part present and hashlimit correct */ - if (DUK_UNLIKELY(slot >= DUK_UINT16_MAX)) { + if (DUK_UNLIKELY(slot >= DUK_UINT8_MAX)) { return; } - idx = ((duk_uint32_t) obj) ^ DUK_HSTRING_GET_HASH(key); - ent = heap->slotcache + (idx & (DUK_HEAP_SLOTCACHE_SIZE - 1)); + 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 75a53c3e19..f1d0d8f8c4 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -1148,7 +1148,9 @@ DUK_INTERNAL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *o 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 -> %ld", (void *) obj, (void *) key, (long) slot)); + 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; @@ -1159,7 +1161,9 @@ DUK_INTERNAL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *o 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 -> %ld", (void *) obj, (void *) key, (long) i)); + 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;