diff --git a/RELEASES.rst b/RELEASES.rst index f114c2c4..c9d4068e 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -2981,7 +2981,8 @@ Planned * Miscellaneous performance improvements: move rare/large opcodes into NOINLINE helpers (GH-1510); duk_harray fast path for internal array unpack for bound functions, .call, and .apply (GH-1525); unsafe internal - value stack pops where safe (GH-1583, GH-1584) + value stack pops where safe (GH-1583, GH-1584); initial object property + table size estimate for NEWOBJ opcode (object literals) (GH-1596) 3.0.0 (XXXX-XX-XX) ------------------ diff --git a/debugger/duk_opcodes.yaml b/debugger/duk_opcodes.yaml index e1e4674e..ca0999ef 100644 --- a/debugger/duk_opcodes.yaml +++ b/debugger/duk_opcodes.yaml @@ -926,6 +926,7 @@ opcodes: - BC_R - name: NEWOBJ args: + - A_I # property count init size - BC_R - name: NEWARR args: diff --git a/src-input/duk_heap.h b/src-input/duk_heap.h index e6c38220..95af933d 100644 --- a/src-input/duk_heap.h +++ b/src-input/duk_heap.h @@ -574,6 +574,8 @@ struct duk_heap { duk_int_t stats_ms_emergency_count; duk_int_t stats_intern_hit; duk_int_t stats_intern_miss; + duk_int_t stats_object_realloc_props; + duk_int_t stats_object_abandon_array; duk_int_t stats_getprop_all; duk_int_t stats_getprop_arrayidx; duk_int_t stats_getprop_bufobjidx; diff --git a/src-input/duk_heap_markandsweep.c b/src-input/duk_heap_markandsweep.c index 5c183a0e..818ab801 100644 --- a/src-input/duk_heap_markandsweep.c +++ b/src-input/duk_heap_markandsweep.c @@ -1065,6 +1065,8 @@ DUK_LOCAL void duk__dump_stats(duk_heap *heap) { (long) heap->stats_ms_emergency_count)); DUK_D(DUK_DPRINT("stats string intern: hit=%ld, miss=%ld", (long) heap->stats_intern_hit, (long) heap->stats_intern_miss)); + DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld", + (long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array)); DUK_D(DUK_DPRINT("stats getprop: all=%ld, arrayidx=%ld, bufobjidx=%ld, " "bufferidx=%ld, bufferlen=%ld, stringidx=%ld, stringlen=%ld, " "proxy=%ld, arguments=%ld", diff --git a/src-input/duk_hobject.h b/src-input/duk_hobject.h index 98ed80f6..780fde97 100644 --- a/src-input/duk_hobject.h +++ b/src-input/duk_hobject.h @@ -868,6 +868,9 @@ DUK_INTERNAL_DECL void duk_hobject_realloc_props(duk_hthread *thr, duk_uint32_t new_a_size, duk_uint32_t new_h_size, duk_bool_t abandon_array); +DUK_INTERNAL_DECL void duk_hobject_resize_props(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_e_size); /* low-level property functions */ DUK_INTERNAL_DECL 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); @@ -903,22 +906,20 @@ DUK_INTERNAL_DECL duk_bool_t duk_hobject_has_finalizer_fast_raw(duk_hobject *obj #endif /* helpers for defineProperty() and defineProperties() */ -DUK_INTERNAL_DECL -void duk_hobject_prepare_property_descriptor(duk_context *ctx, - duk_idx_t idx_in, - duk_uint_t *out_defprop_flags, - duk_idx_t *out_idx_value, - duk_hobject **out_getter, - duk_hobject **out_setter); -DUK_INTERNAL_DECL -duk_bool_t duk_hobject_define_property_helper(duk_context *ctx, - duk_uint_t defprop_flags, - duk_hobject *obj, - duk_hstring *key, - duk_idx_t idx_value, - duk_hobject *get, - duk_hobject *set, - duk_bool_t throw_flag); +DUK_INTERNAL_DECL void duk_hobject_prepare_property_descriptor(duk_context *ctx, + duk_idx_t idx_in, + duk_uint_t *out_defprop_flags, + duk_idx_t *out_idx_value, + duk_hobject **out_getter, + duk_hobject **out_setter); +DUK_INTERNAL_DECL duk_bool_t duk_hobject_define_property_helper(duk_context *ctx, + duk_uint_t defprop_flags, + duk_hobject *obj, + duk_hstring *key, + duk_idx_t idx_value, + duk_hobject *get, + duk_hobject *set, + duk_bool_t throw_flag); /* Object built-in methods */ DUK_INTERNAL_DECL void duk_hobject_object_get_own_property_descriptor(duk_context *ctx, duk_idx_t obj_idx); diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index 718a0ed6..a111bb3a 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -537,6 +537,8 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); + DUK_STATS_INC(thr->heap, stats_object_realloc_props); + /* * Pre resize assertions. */ @@ -692,6 +694,8 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, */ DUK_ASSERT(new_a_size == 0); + DUK_STATS_INC(thr->heap, stats_object_abandon_array); + for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) { duk_tval *tv1; duk_tval *tv2; @@ -956,6 +960,30 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, * Helpers to resize properties allocation on specific needs. */ +DUK_INTERNAL void duk_hobject_resize_props(duk_hthread *thr, + duk_hobject *obj, + duk_uint32_t new_e_size) { + duk_uint32_t old_e_size; + duk_uint32_t new_a_size; + duk_uint32_t new_h_size; + + DUK_ASSERT(thr != NULL); + DUK_ASSERT(obj != NULL); + + old_e_size = DUK_HOBJECT_GET_ESIZE(obj); + if (old_e_size > new_e_size) { + new_e_size = old_e_size; + } +#if defined(DUK_USE_HOBJECT_HASH_PART) + new_h_size = duk__get_default_h_size(new_e_size); +#else + new_h_size = 0; +#endif + new_a_size = DUK_HOBJECT_GET_ASIZE(obj); + + duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); +} + /* Grow entry part allocation for one additional entry. */ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) { duk_uint32_t old_e_used; /* actually used, non-NULL entries */ diff --git a/src-input/duk_js_compiler.c b/src-input/duk_js_compiler.c index 047eb4a4..bf2482f4 100644 --- a/src-input/duk_js_compiler.c +++ b/src-input/duk_js_compiler.c @@ -2955,6 +2955,7 @@ typedef struct { duk_reg_t reg_obj; duk_reg_t temp_start; duk_small_uint_t num_pairs; + duk_small_uint_t num_total_pairs; } duk__objlit_state; DUK_LOCAL void duk__objlit_flush_keys(duk_compiler_ctx *comp_ctx, duk__objlit_state *st) { @@ -2975,6 +2976,7 @@ DUK_LOCAL void duk__objlit_flush_keys(duk_compiler_ctx *comp_ctx, duk__objlit_st st->reg_obj, st->temp_start, st->num_pairs * 2); + st->num_total_pairs += st->num_pairs; st->num_pairs = 0; } DUK__SETTEMP(comp_ctx, st->temp_start); @@ -3006,6 +3008,10 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r duk_small_uint_t max_init_pairs; /* max # of key-value pairs initialized in one MPUTOBJ set */ duk_bool_t first; /* first value: comma must not precede the value */ duk_bool_t is_set, is_get; /* temps */ + duk_int_t pc_newobj; +#if !defined(DUK_USE_PREFER_SIZE) + duk_compiler_instr *instr; +#endif DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LCURLY); @@ -3014,8 +3020,10 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r st.reg_obj = DUK__ALLOCTEMP(comp_ctx); /* target object */ st.temp_start = DUK__GETTEMP(comp_ctx); /* start of MPUTOBJ argument list */ st.num_pairs = 0; /* number of key/value pairs emitted for current MPUTOBJ set */ + st.num_total_pairs = 0; /* number of key/value pairs emitted overall */ - duk__emit_bc(comp_ctx, DUK_OP_NEWOBJ, st.reg_obj); /* XXX: patch initial size hint afterwards? */ + pc_newobj = duk__get_current_pc(comp_ctx); + duk__emit_bc(comp_ctx, DUK_OP_NEWOBJ, st.reg_obj); /* * Emit initializers in sets of maximum max_init_pairs keys. @@ -3203,6 +3211,17 @@ DUK_LOCAL void duk__nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *r DUK_ASSERT(st.num_pairs == 0); DUK_ASSERT(DUK__GETTEMP(comp_ctx) == st.temp_start); + /* Update initial size for NEWOBJ. The init size doesn't need to be + * exact as the purpose is just to avoid object resizes in common + * cases. The size is capped to field A limit, and will be too high + * if the object literal contains duplicate keys (this is harmless but + * increases memory traffic if the object is compacted later on). + */ +#if !defined(DUK_USE_PREFER_SIZE) + instr = duk__get_instr_ptr(comp_ctx, pc_newobj); + instr->ins |= DUK_ENC_OP_A(0, st.num_total_pairs > 255 ? 255 : st.num_total_pairs); +#endif + DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY); duk__advance(comp_ctx); diff --git a/src-input/duk_js_executor.c b/src-input/duk_js_executor.c index 7e35cce4..435dd416 100644 --- a/src-input/duk_js_executor.c +++ b/src-input/duk_js_executor.c @@ -4741,6 +4741,9 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread * case DUK_OP_NEWOBJ: { duk_context *ctx = (duk_context *) thr; duk_push_object(ctx); +#if !defined(DUK_USE_PREFER_SIZE) + duk_hobject_resize_props(thr, duk_known_hobject(ctx, -1), DUK_DEC_A(ins)); +#endif DUK__REPLACE_TOP_BC_BREAK(); } diff --git a/tests/perf/test-object-literal-100.js b/tests/perf/test-object-literal-100.js new file mode 100644 index 00000000..ad4ff74e --- /dev/null +++ b/tests/perf/test-object-literal-100.js @@ -0,0 +1,122 @@ +/* + * Create Object using a literal + */ + +if (typeof print !== 'function') { print = console.log; } + +function test() { + var obj; + var i; + + for (i = 0; i < 1e6; i++) { + obj = { + key1: 'val1', + key2: 'val2', + key3: 'val3', + key4: 'val4', + key5: 'val5', + key6: 'val6', + key7: 'val7', + key8: 'val8', + key9: 'val9', + key10: 'val10', + key11: 'val11', + key12: 'val12', + key13: 'val13', + key14: 'val14', + key15: 'val15', + key16: 'val16', + key17: 'val17', + key18: 'val18', + key19: 'val19', + key20: 'val20', + key21: 'val21', + key22: 'val22', + key23: 'val23', + key24: 'val24', + key25: 'val25', + key26: 'val26', + key27: 'val27', + key28: 'val28', + key29: 'val29', + key30: 'val30', + key31: 'val31', + key32: 'val32', + key33: 'val33', + key34: 'val34', + key35: 'val35', + key36: 'val36', + key37: 'val37', + key38: 'val38', + key39: 'val39', + key40: 'val40', + key41: 'val41', + key42: 'val42', + key43: 'val43', + key44: 'val44', + key45: 'val45', + key46: 'val46', + key47: 'val47', + key48: 'val48', + key49: 'val49', + key50: 'val50', + key51: 'val51', + key52: 'val52', + key53: 'val53', + key54: 'val54', + key55: 'val55', + key56: 'val56', + key57: 'val57', + key58: 'val58', + key59: 'val59', + key60: 'val60', + key61: 'val61', + key62: 'val62', + key63: 'val63', + key64: 'val64', + key65: 'val65', + key66: 'val66', + key67: 'val67', + key68: 'val68', + key69: 'val69', + key70: 'val70', + key71: 'val71', + key72: 'val72', + key73: 'val73', + key74: 'val74', + key75: 'val75', + key76: 'val76', + key77: 'val77', + key78: 'val78', + key79: 'val79', + key80: 'val80', + key81: 'val81', + key82: 'val82', + key83: 'val83', + key84: 'val84', + key85: 'val85', + key86: 'val86', + key87: 'val87', + key88: 'val88', + key89: 'val89', + key90: 'val90', + key91: 'val91', + key92: 'val92', + key93: 'val93', + key94: 'val94', + key95: 'val95', + key96: 'val96', + key97: 'val97', + key98: 'val98', + key99: 'val99', + key100: 'val100' + }; + } +} + +try { + test(); +} catch (e) { + print(e.stack || e); + throw e; +} diff --git a/tests/perf/test-object-literal.js b/tests/perf/test-object-literal-20.js similarity index 100% rename from tests/perf/test-object-literal.js rename to tests/perf/test-object-literal-20.js diff --git a/tests/perf/test-object-literal-3.js b/tests/perf/test-object-literal-3.js new file mode 100644 index 00000000..bbc0faf5 --- /dev/null +++ b/tests/perf/test-object-literal-3.js @@ -0,0 +1,25 @@ +/* + * Create Object using a literal + */ + +if (typeof print !== 'function') { print = console.log; } + +function test() { + var obj; + var i; + + for (i = 0; i < 1e6; i++) { + obj = { + key1: 'val1', + key2: 'val2', + key3: 'val3' + }; + } +} + +try { + test(); +} catch (e) { + print(e.stack || e); + throw e; +}