From 735e0df8e4e8f79256b10959110aa35b466e0cb3 Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Mon, 22 Feb 2016 02:55:42 -0600 Subject: [PATCH 1/3] benchmark: add util.format benchmark PR-URL: https://github.com/nodejs/node/pull/5360 Reviewed-By: James M Snell --- benchmark/util/format.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 benchmark/util/format.js diff --git a/benchmark/util/format.js b/benchmark/util/format.js new file mode 100644 index 00000000000000..6a0b6c63dc1087 --- /dev/null +++ b/benchmark/util/format.js @@ -0,0 +1,40 @@ +'use strict'; + +const util = require('util'); +const common = require('../common'); +const v8 = require('v8'); +const bench = common.createBenchmark(main, { + n: [1e6] +, type: ['string', + 'number', + 'object', + 'unknown', + 'no-replace'] +}); + +const inputs = { + 'string': ['Hello, my name is %s', 'fred'], + 'number': ['Hi, I was born in %d', 1942], + 'object': ['An error occurred %j', {msg: 'This is an error', code: 'ERR'}], + 'unknown': ['hello %a', 'test'], + 'no-replace': [1, 2] +}; + +function main(conf) { + const n = conf.n | 0; + const type = conf.type; + + const input = inputs[type]; + + v8.setFlagsFromString('--allow_natives_syntax'); + + util.format(input[0], input[1]); + eval('%OptimizeFunctionOnNextCall(util.format)'); + util.format(input[0], input[1]); + + bench.start(); + for (var i = 0; i < n; i++) { + util.format(input[0], input[1]); + } + bench.end(n); +} From 8d72b0d291e603c3b62b637a314a5ee35ba95fdc Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Mon, 22 Feb 2016 02:55:55 -0600 Subject: [PATCH 2/3] util: improve util.format performance By manually copying arguments and breaking the try/catch out, we are able to improve the performance of util.format by 20-100% (depending on the types). PR-URL: https://github.com/nodejs/node/pull/5360 Reviewed-By: James M Snell --- lib/util.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/util.js b/lib/util.js index 1425cc6b504c15..a971c103d4ecd7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -9,39 +9,48 @@ const isError = internalUtil.isError; var Debug; +function tryStringify(arg) { + try { + return JSON.stringify(arg); + } catch (_) { + return '[Circular]'; + } +} + const formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (typeof f !== 'string') { - var objects = []; + const objects = new Array(arguments.length); for (var index = 0; index < arguments.length; index++) { - objects.push(inspect(arguments[index])); + objects[index] = inspect(arguments[index]); } return objects.join(' '); } if (arguments.length === 1) return f; - var i = 1; - var args = arguments; - var len = args.length; - var str = String(f).replace(formatRegExp, function(x) { + const len = arguments.length; + const args = new Array(len); + var i; + for (i = 0; i < len; i++) { + args[i] = arguments[i]; + } + + i = 1; + var str = f.replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); - case '%j': - try { - return JSON.stringify(args[i++]); - } catch (_) { - return '[Circular]'; - } + case '%j': return tryStringify(args[i++]); // falls through default: return x; } }); - for (var x = args[i]; i < len; x = args[++i]) { + while (i < len) { + const x = args[i++]; if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) { str += ' ' + x; } else { From c490b8ba54df6c730f7bfd02aaafe7c34f571f9e Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 22 Feb 2016 20:47:20 -0500 Subject: [PATCH 3/3] util: improve format() performance further Replacing the regexp and replace function with a loop improves performance by ~60-200%. PR-URL: https://github.com/nodejs/node/pull/5360 Reviewed-By: James M Snell --- lib/util.js | 73 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/lib/util.js b/lib/util.js index a971c103d4ecd7..42661608ddf7f5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -17,7 +17,6 @@ function tryStringify(arg) { } } -const formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (typeof f !== 'string') { const objects = new Array(arguments.length); @@ -27,30 +26,56 @@ exports.format = function(f) { return objects.join(' '); } - if (arguments.length === 1) return f; - - const len = arguments.length; - const args = new Array(len); - var i; - for (i = 0; i < len; i++) { - args[i] = arguments[i]; - } - - i = 1; - var str = f.replace(formatRegExp, function(x) { - if (x === '%%') return '%'; - if (i >= len) return x; - switch (x) { - case '%s': return String(args[i++]); - case '%d': return Number(args[i++]); - case '%j': return tryStringify(args[i++]); - // falls through - default: - return x; + var argLen = arguments.length; + + if (argLen === 1) return f; + + var str = ''; + var a = 1; + var lastPos = 0; + for (var i = 0; i < f.length;) { + if (f.charCodeAt(i) === 37/*'%'*/ && i + 1 < f.length) { + switch (f.charCodeAt(i + 1)) { + case 100: // 'd' + if (a >= argLen) + break; + if (lastPos < i) + str += f.slice(lastPos, i); + str += Number(arguments[a++]); + lastPos = i = i + 2; + continue; + case 106: // 'j' + if (a >= argLen) + break; + if (lastPos < i) + str += f.slice(lastPos, i); + str += tryStringify(arguments[a++]); + lastPos = i = i + 2; + continue; + case 115: // 's' + if (a >= argLen) + break; + if (lastPos < i) + str += f.slice(lastPos, i); + str += String(arguments[a++]); + lastPos = i = i + 2; + continue; + case 37: // '%' + if (lastPos < i) + str += f.slice(lastPos, i); + str += '%'; + lastPos = i = i + 2; + continue; + } } - }); - while (i < len) { - const x = args[i++]; + ++i; + } + if (lastPos === 0) + str = f; + else if (lastPos < f.length) + str += f.slice(lastPos); + while (a < argLen) { + const x = arguments[a++]; if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) { str += ' ' + x; } else {