ArrayBuffer vs JSON

JavaScript performance comparison

Test case created by romshark

Preparation code


      
      <script>
Benchmark.prototype.setup = function() {
  /* ENCODER UTILITIES */
  
  function writeUnicode(str, view8, offset) {
  	for (let i = 0; i < str.length; i++) {
  		let charcode = str.charCodeAt(i)
  		if (charcode < 0x80) {
  			view8[offset++] = charcode
  		} else if (charcode < 0x800) {
  			view8[offset++] = 0xc0 | (charcode >> 6)
  			view8[offset++] = 0x80 | (charcode & 0x3f)
  		} else if (charcode < 0xd800 || charcode >= 0xe000) {
  			view8[offset++] = 0xe0 | (charcode >> 12)
  			view8[offset++] = 0x80 | ((charcode >> 6) & 0x3f)
  			view8[offset++] = 0x80 | (charcode & 0x3f)
  		} else {
  			// surrogate pair
  
  			i++
  			// UTF-16 encodes 0x10000-0x10FFFF by
  			// subtracting 0x10000 and splitting the
  			// 20 bits of 0x0-0xFFFFF into two halves
  			charcode = 0x10000 + (
  				((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)
  			)
  			view8[offset++] = 0xf0 | (charcode >> 18)
  			view8[offset++] = 0x80 | ((charcode >> 12) & 0x3f)
  			view8[offset++] = 0x80 | ((charcode >> 6) & 0x3f)
  			view8[offset++] = 0x80 | (charcode & 0x3f)
  		}
  	}
  }
  
  // writeUTF16 writes str to the given binary array interface. offset must be
  // power 2
  function writeUTF16(str, view16, offset) {
  	offset /= 2
  	const initialOffset = offset
  	for (let i = 0; i < str.length; i++) {
  		view16[offset++] = str.charCodeAt(i)
  	}
  }
  
  function transformUnicode(str) {
  	const unicode = []
  	for (let i = 0; i < str.length; i++) {
  		let charcode = str.charCodeAt(i)
  		if (charcode < 0x80) unicode.push(charcode)
  		else if (charcode < 0x800) {
  			unicode.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f))
  		} else if (charcode < 0xd800 || charcode >= 0xe000) {
  			unicode.push(
  				0xe0 | (charcode >> 12),
  				0x80 | ((charcode >> 6) & 0x3f),
  				0x80 | (charcode & 0x3f),
  			)
  		} else {
  			// surrogate pair
  
  			i++
  			// UTF-16 encodes 0x10000-0x10FFFF by
  			// subtracting 0x10000 and splitting the
  			// 20 bits of 0x0-0xFFFFF into two halves
  			charcode = 0x10000 + (
  				((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)
  			)
  			unicode.push(
  				0xf0 | (charcode >> 18),
  				0x80 | ((charcode >> 12) & 0x3f),
  				0x80 | ((charcode >> 6) & 0x3f),
  				0x80 | (charcode & 0x3f),
  			)
  		}
  	}
  	return unicode
  }
  
  /* DECODER UTILITIES */
  
  function readUnicode(view8, offset, length) {
  	let c
  	let char2, char3
  	let out = ''
  	length += offset
  	while (offset < length) {
  		c = view8[offset++]
  		switch (c >> 4) {
  		case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
  			// 0xxxxxxx
  			out += String.fromCharCode(c)
  			break
  		case 12: case 13:
  			// 110x xxxx 10xx xxxx
  			char2 = view8[offset++]
  			out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F))
  			break
  		case 14:
  			// 1110 xxxx  10xx xxxx  10xx xxxx
  			char2 = view8[offset++]
  			char3 = view8[offset++]
  			out += String.fromCharCode(
  				((c & 0x0F) << 12) |
  				((char2 & 0x3F) << 6) |
  				((char3 & 0x3F) << 0)
  			)
  			break
  		}
  	}
  	return out
  }
  
  /* GENERATED MESSAGE (Person) ENCODER */
  
  // msgPerson_minSize determines the absolute minimum message buffer size of
  // type "person"
  const msgPerson_minSize = 0
  	+8   // accountValue
  	+4   // id
  	+2   // country
  	+4*3 // 3x string length flag
  
  // msgPerson_encodeUnicode encodes a message of type "person" to a binary array
  // using UTF8 for string values
  function msgPerson_encodeUnicode(person) {
  	// Determine the length of variable-sized string fields
  	const n = transformUnicode(person.name)
  	const e = transformUnicode(person.contact.email)
  	const s = transformUnicode(person.contact.address.street)
  	const n_l = n.length
  	const e_l = e.length
  	const s_l = s.length
  	
  	// Allocate message buffer and determine its size
  	const bin = new ArrayBuffer(msgPerson_minSize + n_l + e_l + s_l)
  
  	// Initialize buffer interfaces
  	const view = new DataView(bin)
  	const view8 = new Uint8Array(bin)
  
  	/* Message structure:
  	step  field  len  offset
  	64    a      8    0
  	32    i      4    8
  	      n_l    4    12
  	      e_l    4    16
  		  s_l    4    20
  	16    c      2    24
  	8     n      n_l  26
  	      e      e_l  26+n_l
  	      s      s_l  26+n_l+e_l
  	*/
  	
  	// Write a at 0
  	view.setFloat64(0, person.accountValue)
  
  	// Write i at 8
  	view.setUint32(8, person.id)
  
  	// Write n_l at 12
  	view.setUint32(12, n_l)
  
  	// Write e_l at 16
  	view.setUint32(16, e_l)
  
  	// Write s_l at 20
  	view.setUint32(20, s_l)
  
  	// Write c at 24
  	view.setUint16(24, person.contact.address.country)
  
  	// Write n at 26
  	const n_written = writeUnicode(
  		person.name,
  		view8,
  		26,
  	)
  
  	// Write e at 26+n_l
  	const e_written = writeUnicode(
  		person.contact.email,
  		view8,
  		26+n_l,
  	)
  
  	// Write s at 26+n_l+e_l
  	const s_written = writeUnicode(
  		person.contact.address.street,
  		view8,
  		26+n_l+e_l,
  	)
  
  	return bin
  }
  
  // msgPerson_encodeUTF16 encodes a message of type "person" to a binary array
  // using UTF16 for string values
  function msgPerson_encodeUTF16(person) {
  	// Determine the length of variable-sized string fields
  	const n_l = person.name.length * 2
  	const e_l = person.contact.email.length * 2
  	const s_l = person.contact.address.street.length * 2
  	
  	// Allocate message buffer and determine its size
  	const bin = new ArrayBuffer(msgPerson_minSize + n_l + e_l + s_l)
  
  	// Initialize buffer interfaces
  	const view = new DataView(bin)
  	const view16 = new Uint16Array(bin)
  
  	/* Message structure:
  	step  field  len  offset
  	64    a      8    0
  	32    i      4    8
  	      n_l    4    12
  	      e_l    4    16
  		  s_l    4    20
  	16    c      2    24
  	      n      n_l  26
  	      e      e_l  26+n_l
  	      s      s_l  26+n_l+e_l
  	*/
  	
  	// Write a at 0
  	view.setFloat64(0, person.accountValue)
  
  	// Write i at 8
  	view.setUint32(8, person.id)
  
  	// Write n_l at 12
  	view.setUint32(12, n_l)
  
  	// Write e_l at 16
  	view.setUint32(16, e_l)
  
  	// Write s_l at 20
  	view.setUint32(20, s_l)
  
  	// Write c at 24
  	view.setUint16(24, person.contact.address.country)
  
  	// Write n at 26
  	writeUTF16(
  		person.name,
  		view16,
  		26,
  	)
  
  	// Write e at 26+n_l
  	writeUTF16(
  		person.contact.email,
  		view16,
  		26+n_l,
  	)
  
  	// Write s at 26+n_l+e_l
  	writeUTF16(
  		person.contact.address.street,
  		view16,
  		26+n_l+e_l,
  	)
  
  	return bin
  }
  
  /* GENERATED MESSAGE (Person) DECODER */
  
  // msgPerson_decodeUnicode decodes a message of type "person" to an object using
  // decoding string values from UTF8
  function msgPerson_decodeUnicode(bin) {
  	if (bin.length < msgPerson_minSize) {
  		throw new Error(
  			`message too small (${bin.length}) to fit a person object (${msgPerson_minSize})`
  		)
  	}
  	
  	// Initialize buffer interfaces
  	const view = new DataView(bin)
  	const view8 = new Uint8Array(bin)
  
  	/* Message structure:
  	step  field  len  offset
  	64    a      8    0
  	32    i      4    8
  	      n_l    4    12
  	      e_l    4    16
  		  s_l    4    20
  	16    c      2    24
  	8     n      n_l  26
  	      e      e_l  26+n_l
  	      s      s_l  26+n_l+e_l
  	*/
  
  	// Initialize person
  	const person = {
  		accountValue: null,
  		id: null,
  		name: null,
  		contact: {
  			email: null,
  			address: {
  				country: null,
  				street: null
  			}
  		}
  	}
  
  	// Read a at 0
  	person.accountValue = view.getFloat64(0)
  
  	// Read i at 8
  	person.id = view.getUint32(8)
  	
  	// Read n_l at 12
  	const n_l = view.getUint32(12)
  
  	// Read e_l at 16
  	const e_l = view.getUint32(16)
  
  	// Read s_l at 20
  	const s_l = view.getUint32(20)
  	
  	// Read c at 24
  	person.contact.address.country = view.getUint16(24)
  
  	// Read n at 26
  	person.name = readUnicode(view8, 26, n_l)
  
  	// Read e at 26+n_l
  	person.contact.email = readUnicode(view8, 26+n_l, e_l)
  
  	// Read s at 26+n_l+e_l
  	person.contact.address.street = readUnicode(view8, 26+n_l+e_l, s_l)
  
  	return person
  }
  
  // msgPerson_decodeUTF16 decodes a message of type "person" to an object using
  // decoding string values from UTF16
  function msgPerson_decodeUTF16(bin) {
  	if (bin.length < msgPerson_minSize) {
  		throw new Error(
  			`message too small (${bin.length}) to fit a person object (${msgPerson_minSize})`
  		)
  	}
  	
  	// Initialize buffer interfaces
  	const view = new DataView(bin)
  	const view16 = new Uint16Array(bin)
  
  	/* Message structure:
  	step  field  len  offset
  	64    a      8    0
  	32    i      4    8
  	      n_l    4    12
  	      e_l    4    16
  		  s_l    4    20
  	16    c      2    24
  	      n      n_l  26
  	      e      e_l  26+n_l
  	      s      s_l  26+n_l+e_l
  	*/
  
  	// Initialize person
  	const person = {
  		accountValue: null,
  		id: null,
  		name: null,
  		contact: {
  			email: null,
  			address: {
  				country: null,
  				street: null
  			}
  		}
  	}
  
  	// Read a at 0
  	person.accountValue = view.getFloat64(0)
  
  	// Read i at 8
  	person.id = view.getUint32(8)
  	
  	// Read n_l at 12
  	const n_l = view.getUint32(12)
  
  	// Read e_l at 16
  	const e_l = view.getUint32(16)
  
  	// Read s_l at 20
  	const s_l = view.getUint32(20)
  	
  	// Read c at 24
  	person.contact.address.country = view.getUint16(24)
  
  	// Read n at 26
  	person.name = String.fromCharCode.apply(
  		null, new Uint16Array(bin, 26, n_l/2)
  	)
  
  	// Read e at 26+n_l
  	person.contact.email = String.fromCharCode.apply(
  		null, new Uint16Array(bin, 26+n_l, e_l/2)
  	)
  
  	// Read s at 26+n_l+e_l
  	person.contact.address.street = String.fromCharCode.apply(
  		null, new Uint16Array(bin, 26+n_l+e_l, s_l/2)
  	)
  
  	return person
  }
  
  /* EXAMPLE DATA */
  
  const data = {
  	accountValue: 3455.34,              // double;  8 byte
  	id: 23,                             // uint;    4 byte
  	name: "фёдор",                      // unicode; 1+n(max:2^32-1) byte
  	contact: {
  		email: "test@person.com",       // unicode; 1+n(max:2^32-1) byte
  		address: {
  			country: 42,                // uint16;  2 byte
  			street: "Bügelsägen Str 13" // unicode; 2+n(max:2^32-1) byte
  		}
  	}
  }
  
  /* TEST */
  
  const testEncodedJSON = JSON.stringify(data)
  const testEncodedBinaryUTF8 = msgPerson_encodeUnicode(data)
  const testEncodedBinaryUTF16 = msgPerson_encodeUTF16(data)
  

};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
JSON Encode
JSON.stringify(data)
pending…
JSON Decode
JSON.parse(testEncodedJSON)
pending…
Binary Encode (unicode)
msgPerson_encodeUnicode(data)
pending…
Binary Decode (unicode)
msgPerson_decodeUnicode(testEncodedBinaryUTF8)
pending…
Binary Encode (UTF16)
msgPerson_encodeUTF16(data)
pending…
Binary Decode (UTF16)
msgPerson_decodeUTF16(testEncodedBinaryUTF16)
pending…

You can edit these tests or add even more tests to this page by appending /edit to the URL.

Compare results of other browsers

0 Comments