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); +} diff --git a/lib/util.js b/lib/util.js index 1425cc6b504c15..42661608ddf7f5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -9,39 +9,73 @@ const isError = internalUtil.isError; var Debug; -const formatRegExp = /%[sdj%]/g; +function tryStringify(arg) { + try { + return JSON.stringify(arg); + } catch (_) { + return '[Circular]'; + } +} + 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) { - 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]'; - } - // 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; + } } - }); - for (var x = args[i]; i < len; 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 {