From b4268285f3b77d9c4b6d3a365e97b3bc364de362 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sun, 4 Jan 2026 20:33:55 +0100 Subject: [PATCH] Optimize String.prototype.concat Optimize the common case where the inputs are strings. Speeds up the string_concat2 and string_concat3 benchmarks by about 1.8x and 2.1x, the string_concat0 and string_concat1 results are unchanged. (string_concat1 became a few percent points faster but it borders on noise.) --- quickjs.c | 40 ++++++++++++++++++++++++++++++++++++---- tests/microbench.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/quickjs.c b/quickjs.c index 80fa17cdd..3345e7fda 100644 --- a/quickjs.c +++ b/quickjs.c @@ -43542,12 +43542,44 @@ static JSValue js_string_codePointAt(JSContext *ctx, JSValueConst this_val, static JSValue js_string_concat(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + int i, is_wide_char; + JSString *p, *q; + int64_t len; + uint32_t n; JSValue r; - int i; - /* XXX: Use more efficient method */ - /* XXX: This method is OK if r has a single refcount */ - /* XXX: should use string_buffer? */ + if (JS_TAG_STRING != JS_VALUE_GET_TAG(this_val)) + goto slow_path; + if (!argc) + return js_dup(this_val); + p = JS_VALUE_GET_STRING(this_val); + len = p->len; + is_wide_char = p->is_wide_char; + for (i = 0; i < argc; i++) { + if (JS_TAG_STRING != JS_VALUE_GET_TAG(argv[i])) + goto slow_path; + p = JS_VALUE_GET_STRING(argv[i]); + if (p->is_wide_char != is_wide_char) + goto slow_path; + len += p->len; + } + if (len > INT_MAX) + return JS_ThrowOutOfMemory(ctx); + q = js_alloc_string(ctx, len, is_wide_char); + if (!q) + return JS_EXCEPTION; + p = JS_VALUE_GET_STRING(this_val); + n = p->len << is_wide_char; + memcpy(str8(q), str8(p), n); + len = n; + for (i = 0; i < argc; i++) { + p = JS_VALUE_GET_STRING(argv[i]); + n = p->len << is_wide_char; + memcpy(str8(q) + len, str8(p), n); + len += n; + } + return JS_MKPTR(JS_TAG_STRING, q); +slow_path: r = JS_ToStringCheckObject(ctx, this_val); for (i = 0; i < argc; i++) { if (JS_IsException(r)) diff --git a/tests/microbench.js b/tests/microbench.js index 445c6c026..a7d3bae52 100644 --- a/tests/microbench.js +++ b/tests/microbench.js @@ -830,6 +830,37 @@ function string_slice3(n) return n * 1000; } +function string_concat(n, r) +{ + var i, f, xs; + // generate a function on the fly; splatting the arguments with + // "x".concat(...xs) spends too much time inside the splat itself + xs = "x".repeat(r).split("").map(_ => "'x'"); + f = new Function("", `'x'.concat(${ xs.join(",") })`); + for (i = 0; i < n; i++) + f(); + return n; +} + +function string_concat0(n) { + return string_concat(n, 0); +} + +function string_concat1(n) +{ + return string_concat(n, 1); +} + +function string_concat2(n) +{ + return string_concat(n, 32); +} + +function string_concat3(n) +{ + return string_concat(n, 256); +} + /* sort bench */ function sort_bench(text) { @@ -1147,6 +1178,10 @@ function main(argc, argv, g) string_build2, //string_build3, //string_build4, + string_concat0, + string_concat1, + string_concat2, + string_concat3, string_slice1, string_slice2, string_slice3,