Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

querystring: improve unescapeBuffer performance #10837

Merged
merged 1 commit into from
Jan 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions benchmark/querystring/querystring-unescapebuffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
var common = require('../common.js');
var querystring = require('querystring');

var bench = common.createBenchmark(main, {
input: [
'there is nothing to unescape here',
'there%20are%20several%20spaces%20that%20need%20to%20be%20unescaped',
'there%2Qare%0-fake%escaped values in%%%%this%9Hstring',
'%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37'
],
n: [10e6],
});

function main(conf) {
var input = conf.input;
var n = conf.n | 0;

bench.start();
for (var i = 0; i < n; i += 1)
querystring.unescapeBuffer(input);
bench.end(n);
}
54 changes: 35 additions & 19 deletions lib/querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,41 @@ const Buffer = require('buffer').Buffer;
function ParsedQueryString() {}
ParsedQueryString.prototype = Object.create(null);


const unhexTable = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47
+0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, // 48 - 63
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ...
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // ... 255
];
// a safe fast alternative to decodeURIComponent
function unescapeBuffer(s, decodeSpaces) {
var out = Buffer.allocUnsafe(s.length);
var state = 0;
var n, m, hexchar;
var n, m, hexchar, c;

for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) {
var c = inIndex < s.length ? s.charCodeAt(inIndex) : NaN;
for (var inIndex = 0, outIndex = 0; ; inIndex++) {
if (inIndex < s.length) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, to anyone who might ask why I didn't put this conditional in the for conditional and the else block immediately after the end of the for loop, it's because there is actually a 2% reduction in the performance increase for the 'fake escaped values' input. The results for the other inputs seem to be largely unaffected though.

c = s.charCodeAt(inIndex);
} else {
if (state > 0) {
out[outIndex++] = 37/*%*/;
if (state === 2)
out[outIndex++] = hexchar;
}
break;
}
switch (state) {
case 0: // Any character
switch (c) {
Expand All @@ -51,13 +77,8 @@ function unescapeBuffer(s, decodeSpaces) {

case 1: // First hex digit
hexchar = c;
if (c >= 48/*0*/ && c <= 57/*9*/) {
n = c - 48/*0*/;
} else if (c >= 65/*A*/ && c <= 70/*F*/) {
n = c - 65/*A*/ + 10;
} else if (c >= 97/*a*/ && c <= 102/*f*/) {
n = c - 97/*a*/ + 10;
} else {
n = unhexTable[c];
if (!(n >= 0)) {
out[outIndex++] = 37/*%*/;
out[outIndex++] = c;
state = 0;
Expand All @@ -68,13 +89,8 @@ function unescapeBuffer(s, decodeSpaces) {

case 2: // Second hex digit
state = 0;
if (c >= 48/*0*/ && c <= 57/*9*/) {
m = c - 48/*0*/;
} else if (c >= 65/*A*/ && c <= 70/*F*/) {
m = c - 65/*A*/ + 10;
} else if (c >= 97/*a*/ && c <= 102/*f*/) {
m = c - 97/*a*/ + 10;
} else {
m = unhexTable[c];
if (!(m >= 0)) {
out[outIndex++] = 37/*%*/;
out[outIndex++] = hexchar;
out[outIndex++] = c;
Expand All @@ -87,7 +103,7 @@ function unescapeBuffer(s, decodeSpaces) {

// TODO support returning arbitrary buffers.

return out.slice(0, outIndex - 1);
return out.slice(0, outIndex);
}


Expand Down
7 changes: 7 additions & 0 deletions test/parallel/test-querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ assert.strictEqual(0xd8, b[17]);
assert.strictEqual(0xa2, b[18]);
assert.strictEqual(0xe6, b[19]);

assert.strictEqual(qs.unescapeBuffer('a+b', true).toString(), 'a b');
assert.strictEqual(qs.unescapeBuffer('a%').toString(), 'a%');
assert.strictEqual(qs.unescapeBuffer('a%2').toString(), 'a%2');
assert.strictEqual(qs.unescapeBuffer('a%20').toString(), 'a ');
assert.strictEqual(qs.unescapeBuffer('a%2g').toString(), 'a%2g');
assert.strictEqual(qs.unescapeBuffer('a%%').toString(), 'a%%');


// Test custom decode
function demoDecode(str) {
Expand Down