From 239bf75c7308cd80e6ec3e1fd45804f4d301bfa2 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 31 Jan 2016 14:57:10 -0500 Subject: [PATCH] querystring: improve escape() performance This commit improves escape() performance by up to 15% with the existing querystring-stringify benchmarks by reducing the number of string concatentations. A potential deopt is also avoided by making sure the index passed to charCodeAt() is within bounds. PR-URL: https://github.com/nodejs/node/pull/5012 Reviewed-By: James M Snell Reviewed-By: Roman Reiss Reviewed-By: Matteo Collina --- .../querystring/querystring-stringify.js | 3 +- lib/querystring.js | 32 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/benchmark/querystring/querystring-stringify.js b/benchmark/querystring/querystring-stringify.js index d4bb95c21e5af4..a22c3ea644c0d1 100644 --- a/benchmark/querystring/querystring-stringify.js +++ b/benchmark/querystring/querystring-stringify.js @@ -4,7 +4,7 @@ var v8 = require('v8'); var bench = common.createBenchmark(main, { type: ['noencode', 'encodemany', 'encodelast'], - n: [1e6], + n: [1e7], }); function main(conf) { @@ -37,6 +37,7 @@ function main(conf) { v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(querystring.stringify)'); + querystring.stringify(input); bench.start(); for (var i = 0; i < n; i += 1) diff --git a/lib/querystring.js b/lib/querystring.js index e0666a45baeefa..b56ad77012d035 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -90,16 +90,13 @@ for (var i = 0; i < 256; ++i) QueryString.escape = function(str) { // replaces encodeURIComponent // http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 - str = '' + str; - var len = str.length; + if (typeof str !== 'string') + str += ''; var out = ''; - var i, c; - - if (len === 0) - return str; + var lastPos = 0; - for (i = 0; i < len; ++i) { - c = str.charCodeAt(i); + for (var i = 0; i < str.length; ++i) { + var c = str.charCodeAt(i); // These characters do not need escaping (in order): // ! - . _ ~ @@ -112,22 +109,27 @@ QueryString.escape = function(str) { (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A)) { - out += str[i]; continue; } + if (i - lastPos > 0) + out += str.slice(lastPos, i); + // Other ASCII characters if (c < 0x80) { + lastPos = i + 1; out += hexTable[c]; continue; } // Multi-byte characters ... if (c < 0x800) { + lastPos = i + 1; out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]; continue; } if (c < 0xD800 || c >= 0xE000) { + lastPos = i + 1; out += hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; @@ -135,12 +137,22 @@ QueryString.escape = function(str) { } // Surrogate pair ++i; - c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF)); + var c2; + if (i < str.length) + c2 = str.charCodeAt(i) & 0x3FF; + else + c2 = 0; + lastPos = i + 1; + c = 0x10000 + (((c & 0x3FF) << 10) | c2); out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]; } + if (lastPos === 0) + return str; + if (lastPos < str.length) + return out + str.slice(lastPos); return out; };