jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
JSON.stringify (if avail) vs custom implementations
<script>
function ValueNoise(width, height, startOctave, endOctave, persistence, smoothAmount, postprocess)
{
// ie9 does not support Float32Array, which is 2 time faster
// ie9 does not support localStorage for local file
//var valueNoiseMap = new Float32Array(width * height);
var valueNoiseMap = [];
valueNoiseMap.length = width * height;
// We're storing the random data samples in a quadtree
// octave 0 is the whole area
// octave 1 is the area divided by 4
// octave n is the previous octave with each area divided by 4
//var startOctave = 3;
// Go to the pixel level. This algorithm assumes base 2 area
//var endOctave = 7; //Math.log(width) / Math.log(2) - 2;
// We need 4 points to do bilinear interpolation from for the noise generation for each octave.
// This is the summation of Math.pow(2, i + 1) - Math.pow(2, i) + 1 which represents the
// number of corners per depth of a quadtree. So depth zero has 4 and depth one has 9.
var nodeCount = 1 / 3 * (3 * (endOctave + 1) + 3 * Math.pow(2, (endOctave + 1) + 2) + Math.pow(2, 2 * (endOctave + 1) + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
var randomTree = JSON.parse(window.localStorage.getItem(nodeCount));
//var randomTree;
if(!randomTree)
{
//randomTree = new Float32Array(nodeCount);
randomTree = [];
randomTree.length = nodeCount;
for (var i = 0; i < randomTree.length; ++i)
{
randomTree[i] = Math.random();
}
window.localStorage.setItem(nodeCount, JSON.stringify(randomTree));
}
// Make it tileable
for (var i = startOctave; i <= endOctave; ++i)
{
var octaveSize = Math.pow(2, i + 1) - Math.pow(2, i) + 1;
var indexOffset = 1 / 3 * (3 * i + 3 * Math.pow(2, i + 2) + Math.pow(2, 2 * i + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
for(var y = 0; y < octaveSize; ++y)
{
randomTree[indexOffset + y * octaveSize] = randomTree[indexOffset + y * octaveSize + octaveSize - 1];
}
for(var x = 0; x < octaveSize; ++x)
{
randomTree[indexOffset + x] = randomTree[indexOffset + (octaveSize - 1) * octaveSize + x];
}
}
for(var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
valueNoiseMap[y * width + x] = 0;
for (var i = startOctave; i <= endOctave; ++i)
{
var cellSize = width / Math.pow(2, i);
var integerX = Math.floor(x / cellSize);
var integerY = Math.floor(y / cellSize);
var indexOffset = 1 / 3 * (3 * i + 3 * Math.pow(2, i + 2) + Math.pow(2, 2 * i + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
var fractionalX = (x - integerX * cellSize) / cellSize;
var fractionalY = (y - integerY * cellSize) / cellSize;
//Log(cellSize + " " + fractionalX + " " + fractionalY);
var octaveSize = Math.pow(2, i + 1) - Math.pow(2, i) + 1;
var i1 = Interpolate(randomTree[indexOffset + integerY * octaveSize + integerX],
randomTree[indexOffset + integerY * octaveSize + integerX + 1],
fractionalX);
var i2 = Interpolate(randomTree[indexOffset + (integerY + 1) * octaveSize + integerX],
randomTree[indexOffset + (integerY + 1) * octaveSize + integerX + 1],
fractionalX);
valueNoiseMap[y * width + x] += Interpolate(i1 , i2 , fractionalY) * Math.pow(persistence, i - startOctave);
// Smooth and then normalize at the very end
}
}
}
Smooth(width, height, valueNoiseMap, smoothAmount);
Normalize(width, height, valueNoiseMap, 0, 1);
if (postprocess)
{
postprocess(valueNoiseMap);
}
return valueNoiseMap;
}
function Smooth(width, height, noise, amount)
{
// Smooth
for (var i = 0; i < amount; ++i)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
var xMinus1 = x == 0 ? width - 1 : x - 1;
var yMinus1 = y == 0 ? height - 1 : y - 1;
var xPlus1 = (x + 1) % width;
var yPlus1 = (y + 1) % height;
var corners = (noise[yMinus1 * width + xMinus1] +
noise[yMinus1 * width + xPlus1] +
noise[yPlus1 * width + xPlus1] +
noise[yPlus1 * width + xMinus1]) / 16.0;
var sides = (noise[y * width + xMinus1] +
noise[y * width + xPlus1] +
noise[yMinus1 * width + x] +
noise[yPlus1 * width + x]) / 8.0;
var center = noise[y * width + x] / 4.0;
noise[y * width + x] = corners + sides + center;
}
}
}
}
function Normalize(width, height, noise, minimum, maximum)
{
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
// Calculate min and max range used to normalize with
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
min = Math.min(min, noise[y * width + x]);
max = Math.max(max, noise[y * width + x]);
}
}
// Normalize the range to 0 to 1
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = (noise[y * width + x] - min) / (max - min) * (maximum - minimum) + minimum;
}
}
}
function Interpolate(a, b, x)
{
var ft = x * 3.1415927;
var f = (1 - Math.cos(ft)) * 0.5;
return a * (1 - f) + b * f;
}
// console throw exception before F12 in IE9
try{
console
}catch(e){
console={}; console.log = function(s){};
}
// -----------------------------------------------------------------------------
// Native Object extensions
Number.prototype.map = function(istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((this - istart) / (istop - istart));
};
Number.prototype.limit = function(min, max) {
return Math.min(max, Math.max(min, this));
};
Number.prototype.round = function(precision) {
precision = Math.pow(10, precision || 0);
return Math.round(this * precision) / precision;
};
Number.prototype.floor = function() {
return Math.floor(this);
};
Number.prototype.ceil = function() {
return Math.ceil(this);
};
Number.prototype.toInt = function() {
return (this | 0);
};
Number.prototype.toRad = function() {
return (this / 180) * Math.PI;
};
Number.prototype.toDeg = function() {
return (this * 180) / Math.PI;
};
Array.prototype.erase = function(item) {
for( var i = this.length; i--; ) {
if( this[i] === item ) {
this.splice(i, 1);
}
}
return this;
};
Array.prototype.random = function() {
return this[ Math.floor(Math.random() * this.length) ];
};
Function.prototype.bind = function(bind) {
var self = this;
return function(){
var args = Array.prototype.slice.call(arguments);
return self.apply(bind || null, args);
};
};
function assert(condition, msg) {
msg = msg || "assert error";
if (!condition) {
throw msg;
}
};
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify_c !== 'function') {
JSON.stringify_c = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse_c !== 'function') {
JSON.parse_c = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
</script>
var shortJSON = {
"fathers|5-10" : [
{
"married|0-1" : true,
"name" : "@MALE_FIRST_NAME @LAST_NAME",
"sons" : null,
"daughters|0-3" : [
{
"age|0-31" : 0,
"name" : "@FEMALE_FIRST_NAME"
}
]
}
]
};
var longJSON = {
"fathers" : [
{
"married" : false,
"name" : "William Martinez",
"sons" : null,
"daughters" : [
{
"age" : 8,
"name" : "Michelle"
},
{
"age" : 29,
"name" : "Donna"
}
]
},
{
"married" : false,
"name" : "Thomas Taylor",
"sons" : null,
"daughters" : [
{
"age" : 6,
"name" : "Angela"
}
]
},
{
"married" : false,
"name" : "Christopher Johnson",
"sons" : null,
"daughters" : [
{
"age" : 11,
"name" : "Laura"
},
{
"age" : 27,
"name" : "Shirley"
}
]
},
{
"married" : true,
"name" : "Christopher Davis",
"sons" : null,
"daughters" : [
{
"age" : 17,
"name" : "Carol"
},
{
"age" : 28,
"name" : "Michelle"
}
]
},
{
"married" : false,
"name" : "Christopher Jackson",
"sons" : null,
"daughters" : [
{
"age" : 4,
"name" : "Linda"
},
{
"age" : 29,
"name" : "Betty"
}
]
},
{
"married" : false,
"name" : "Michael Harris",
"sons" : null,
"daughters" : [
{
"age" : 8,
"name" : "Carol"
},
{
"age" : 26,
"name" : "Deborah"
},
{
"age" : 14,
"name" : "Sharon"
}
]
}
]
};
var customStringify1 = function stringify(o){
var type = typeof o, tmp, k;
var objectToString = Object.prototype.toString;
if (type === 'string') {
return '"'+o+'"';
}
else if (type === 'number') {
return o;
}
else if (type === 'object') {
tmp = objectToString.call(o);
if(tmp ==='[object Object]') {
tmp = '{';
for (k in o) {
if(o.hasOwnProperty(k))
tmp+= '"'+k+'":'+stringify(o[k])+",";
}
tmp = tmp.slice(0,-1)+"}";
return tmp;
}
else if(tmp ==='[object Array]') {
k = o.length;
while (k--) {
o[k] = stringify(o[k]);
}
return '['+o.join(',')+']';
}
else {
return String(o);
}
}
};
var customStringify2 = function (obj) {
var t = typeof (obj);
if (t != "object" || obj === null) {
// simple data type
if (t == "string") obj = '"'+obj+'"';
return String(obj);
}
else {
// recurse array or object
var n, v, json = [], arr = (obj && obj.constructor == Array);
for (n in obj) {
v = obj[n]; t = typeof(v);
if (t == "string") v = '"'+v+'"';
else if (t == "object" && v !== null) v = JSON.stringify(v);
json.push((arr ? "" : '"' + n + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
};
Ready to run.
Test | Ops/sec | |
---|---|---|
JSON.stringify (internal) |
| ready |
customStringify1 |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.