From e8572e7070bdf4cd291b66db1563d57948e3e03d Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 31 May 2020 01:18:59 -0400 Subject: [PATCH] querystring: improve stringify() performance PR-URL: https://github.com/nodejs/node/pull/33669 Reviewed-By: Benjamin Gruenbaum --- .../querystring/querystring-stringify.js | 9 ++++-- lib/internal/querystring.js | 24 ++++++++++------ lib/querystring.js | 28 +++++++++++++++++-- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/benchmark/querystring/querystring-stringify.js b/benchmark/querystring/querystring-stringify.js index be81f67fa826d7..d1b724fcf2c14d 100644 --- a/benchmark/querystring/querystring-stringify.js +++ b/benchmark/querystring/querystring-stringify.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const querystring = require('querystring'); const bench = common.createBenchmark(main, { - type: ['noencode', 'encodemany', 'encodelast', 'array'], + type: ['noencode', 'encodemany', 'encodelast', 'array', 'multiprimitives'], n: [1e6], }); @@ -28,7 +28,12 @@ function main({ type, n }) { foo: [], baz: ['bar'], xyzzy: ['bar', 'quux', 'thud'] - } + }, + multiprimitives: { + foo: false, + bar: -13.37, + baz: '', + }, }; const input = inputs[type]; diff --git a/lib/internal/querystring.js b/lib/internal/querystring.js index 7df1c495c6e5c5..ee589e1984294d 100644 --- a/lib/internal/querystring.js +++ b/lib/internal/querystring.js @@ -36,19 +36,25 @@ function encodeStr(str, noEscapeTable, hexTable) { let out = ''; let lastPos = 0; + let i = 0; - for (let i = 0; i < len; i++) { + outer: + for (; i < len; i++) { let c = str.charCodeAt(i); // ASCII - if (c < 0x80) { - if (noEscapeTable[c] === 1) - continue; - if (lastPos < i) - out += str.slice(lastPos, i); - lastPos = i + 1; - out += hexTable[c]; - continue; + while (c < 0x80) { + if (noEscapeTable[c] !== 1) { + if (lastPos < i) + out += str.slice(lastPos, i); + lastPos = i + 1; + out += hexTable[c]; + } + + if (++i === len) + break outer; + + c = str.charCodeAt(i); } if (lastPos < i) diff --git a/lib/querystring.js b/lib/querystring.js index 954b35d69e3fbd..4c1b9f87d94e11 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -26,6 +26,7 @@ const { Array, ArrayIsArray, + MathAbs, ObjectCreate, ObjectKeys, } = primordials; @@ -162,6 +163,25 @@ function stringifyPrimitive(v) { } +function encodeStringified(v, encode) { + if (typeof v === 'string') + return (v.length ? encode(v) : ''); + if (typeof v === 'number' && isFinite(v)) { + // Values >= 1e21 automatically switch to scientific notation which requires + // escaping due to the inclusion of a '+' in the output + return (MathAbs(v) < 1e21 ? '' + v : encode('' + v)); + } + if (typeof v === 'boolean') + return v ? 'true' : 'false'; + return ''; +} + + +function encodeStringifiedCustom(v, encode) { + return encode(stringifyPrimitive(v)); +} + + function stringify(obj, sep, eq, options) { sep = sep || '&'; eq = eq || '='; @@ -170,6 +190,8 @@ function stringify(obj, sep, eq, options) { if (options && typeof options.encodeURIComponent === 'function') { encode = options.encodeURIComponent; } + const convert = + (encode === qsEscape ? encodeStringified : encodeStringifiedCustom); if (obj !== null && typeof obj === 'object') { const keys = ObjectKeys(obj); @@ -179,7 +201,7 @@ function stringify(obj, sep, eq, options) { for (let i = 0; i < len; ++i) { const k = keys[i]; const v = obj[k]; - let ks = encode(stringifyPrimitive(k)); + let ks = convert(k, encode); ks += eq; if (ArrayIsArray(v)) { @@ -188,13 +210,13 @@ function stringify(obj, sep, eq, options) { const vlast = vlen - 1; for (let j = 0; j < vlen; ++j) { fields += ks; - fields += encode(stringifyPrimitive(v[j])); + fields += convert(v[j], encode); if (j < vlast) fields += sep; } } else { fields += ks; - fields += encode(stringifyPrimitive(v)); + fields += convert(v, encode); } if (i < flast)