Created
February 10, 2017 21:22
-
-
Save baptistemanson/a5f6f78ffc38b0e8f141e88a00c76a63 to your computer and use it in GitHub Desktop.
JS fast packing with JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* FAST PACKING / UNPACKING JSON | |
* | |
* If all objects in a collection follows a similar schema, | |
* then there is gain in changing the representation from a dictionary to a simple array. | |
* | |
* It is known results used in database, protocols, in v8 itself with shadow maps and IRL. | |
* | |
* In this example, we expect our final exchange to be equivalent to this literal representation: | |
* [ | |
* {id:1,name:'first'}, | |
* {id:2,name:'second'}, | |
* {id:3,name:'third'}, | |
* {id:4,name:'fourth'} | |
* ] | |
* | |
* We show that, knowing the schema, the following representation is better in speed for the whole chain and in space as well. | |
* [ | |
* [1, 'first'], | |
* [2, 'second'], | |
* [3, 'third'], | |
* [4, 'fourth'] | |
* ] | |
* ] | |
* This example works for this schema, but can be easily generalized for any tabular data structure. | |
*/ | |
/** | |
* Packs a collection based on a schema function. | |
* | |
* It is equivalent to ` collection.map(packFunc) .` .map() was just utterly slow on nodejs 7.0. | |
* | |
*/ | |
let packer = (collection, packFunc) => { | |
let l = collection.length; | |
var result = new Array(l) | |
for(var i=0; i<l; i++) { | |
result[i] = packFunc(collection[i]) | |
} | |
return result | |
} | |
/** | |
* Unpacks a collection based on an schemaConstructor | |
* | |
* It is iterating over the collection, using the constructor on each element. Can also be a one liner with .map() | |
*/ | |
let unpacker = (collection, schemaConstructor) => { | |
let l = collection.length; | |
var result = new Array(l) | |
for(var i=0; i<l; i++) { | |
result[i] = new schemaConstructor(collection[i]) | |
} | |
return result | |
} | |
// Function that maps one packed array to an object | |
function unpack (arr) { | |
this.id = arr[0] | |
this.name = arr[1] | |
} | |
// Function that maps one object to one pack array. | |
let pack = element => [element.id, element.name] | |
// Generates test fixtures | |
let mockGen = collectionLength => { | |
let collection = new Array(collectionLength) | |
for (var i = 0; i < collectionLength; i++) { | |
collection[i] = {id:i, name:'randomStrings'} | |
} | |
return collection | |
} | |
/** Display utilities */ | |
var now = require("performance-now") | |
let t = {} | |
let mark = label => t[label] = now() | |
let delta = (labelStart, labelEnd, index) => console.log( (t[labelEnd] - t[labelStart]).toFixed(3), index) | |
/** | |
* | |
* MAIN | |
* | |
*/ | |
console.log('generating a collection of a 1,000 objects') | |
let expectedResults = mockGen(1000) | |
console.log('encoding data / JSON.stringify...') | |
mark(0) | |
let naiveJSON = JSON.stringify(expectedResults) | |
mark(1) | |
let packJSON = JSON.stringify(packer(expectedResults, pack)) | |
mark(2) | |
delta(0,1, 'ms 1 - naive server encoding') | |
delta(1,2, 'ms 2 - packed server encoding') | |
console.log('size / uncompressed') | |
console.log(Math.round(naiveJSON.length/1000) + ' kB for 1 - naive') | |
console.log(Math.round(packJSON.length/1000) + ' kB for 2 - packed') | |
console.log('parsing data / JSON.parse...') | |
mark(10) | |
var i = 10000 | |
while(i--) result = JSON.parse(naiveJSON) | |
mark(11) | |
var i = 10000 | |
while(i--) result = unpacker(JSON.parse(packJSON), unpack) | |
mark(12) | |
delta(10,11, 'ms 1 - naive client decoding') | |
delta(11,12, 'ms 2 - packed client decoding') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment