binary-vs-json

JavaScript performance comparison

Test case created by romshark

Preparation code


      
      <script>
Benchmark.prototype.setup = function() {
  /* ENCODER UTILITIES */
  
  // TextEncoder polyfill
  if (typeof TextEncoder === "undefined") {
    TextEncoder=function TextEncoder(){};
    TextEncoder.prototype.encode = function encode(str) {

      "use strict";
      var Len = str.length, resPos = -1;
      // The Uint8Array's length must be at least 3x the length of the string because an invalid UTF-16
      //  takes up the equivelent space of 3 UTF-8 characters to encode it properly. However, Array's
      //  have an auto expanding length and 1.5x should be just the right balance for most uses.
      var resArr = typeof Uint8Array === "undefined" ? new Array(Len * 1.5) : new Uint8Array(Len * 3);
      for (var point=0, nextcode=0, i = 0; i !== Len; ) {
        point = str.charCodeAt(i), i += 1;
        if (point >= 0xD800 && point <= 0xDBFF) {
          if (i === Len) {
            resArr[resPos += 1] = 0xef/*0b11101111*/; resArr[resPos += 1] = 0xbf/*0b10111111*/;
            resArr[resPos += 1] = 0xbd/*0b10111101*/; break;
          }
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          nextcode = str.charCodeAt(i);
          if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
            point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
            i += 1;
            if (point > 0xffff) {
              resArr[resPos += 1] = (0x1e/*0b11110*/<<3) | (point>>>18);
              resArr[resPos += 1] = (0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/);
              resArr[resPos += 1] = (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/);
              resArr[resPos += 1] = (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/);
              continue;
            }
          } else {
            resArr[resPos += 1] = 0xef/*0b11101111*/; resArr[resPos += 1] = 0xbf/*0b10111111*/;
            resArr[resPos += 1] = 0xbd/*0b10111101*/; continue;
          }
        }
        if (point <= 0x007f) {
          resArr[resPos += 1] = (0x0/*0b0*/<<7) | point;
        } else if (point <= 0x07ff) {
          resArr[resPos += 1] = (0x6/*0b110*/<<5) | (point>>>6);
          resArr[resPos += 1] = (0x2/*0b10*/<<6)  | (point&0x3f/*0b00111111*/);
        } else {
          resArr[resPos += 1] = (0xe/*0b1110*/<<4) | (point>>>12);
          resArr[resPos += 1] = (0x2/*0b10*/<<6)  | ((point>>>6)&0x3f/*0b00111111*/);
          resArr[resPos += 1] = (0x2/*0b10*/<<6)  | (point&0x3f/*0b00111111*/);
        }
      }
      if (typeof Uint8Array !== "undefined") return resArr.subarray(0, resPos + 1);
      // else // IE 6-9
      resArr.length = resPos + 1; // trim off extra weight
      return resArr;
    };
    TextEncoder.prototype.toString = function(){return "[object TextEncoder]"};
    try { // Object.defineProperty only works on DOM prototypes in IE8
      Object.defineProperty(TextEncoder.prototype,"encoding",{
        get:function(){if(TextEncoder.prototype.isPrototypeOf(this)) return"utf-8";
                 else throw TypeError("Illegal invocation");}
      });
    } catch(e) { /*IE6-8 fallback*/ TextEncoder.prototype.encoding = "utf-8"; }
    if(typeof Symbol!=="undefined")TextEncoder.prototype[Symbol.toStringTag]="TextEncoder";
  }
  
  function str2unicode(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 = ''
    if (offset > 0) {
      view8 = view8.slice(offset, offset + length)
      offset = 0
    } else {
      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
  }
  
  function TextEncoding(encoding, native) {
    if (encoding != 'utf-16-le' && encoding != 'utf-8') {
      throw new Error(`unexpected text codec type: ${encoding}`)
    }
  
    this.native = native ? true : false
    this.unicode = encoding === 'utf-8' ? true : false
  
    if (this.native) {
      if (this.unicode) {
        // Native UTF-8
        this.encoder = new TextEncoder('utf-8')
        this.decoder = new TextDecoder('utf-8')
  
        this.encode = function(str) {
          return this.encoder.encode(str)
        }
  
        this.decode = function(view8, offset, length) {
          return this.decoder.decode(view8.slice(offset, offset + length))
        }
  
      } else {
        // Native UTF-16LE
        throw new Error("Native UTF-16LE encoding is no longer supported")
      }
    } else {
      if (this.unicode) {
        // UTF-8
        this.encode = function(str) {
          return new Uint8Array(str2unicode(str))
        }
  
        this.decode = function(view8, offset, length) {
          return readUnicode(view8, offset, length)
        }
  
      } else {
        // UTF-16LE
        this.encode = function(str) {
          const buf = new ArrayBuffer(str.length*2)
          const view16 = new Uint16Array(buf)
          for (let i = 0; i < str.length; i++) {
            view16[i] = str.charCodeAt(i)
          }
          return new Uint8Array(buf)
        }
  
        this.decode = function(view16, offset, length) {
          offset /= 2
          length /= 2
          return String.fromCharCode.apply(
            null,
            view16.slice(offset, offset + length),
          )
        }
      }
    }
  }
  
  // mergeArrays merges all given Uint8Array instances to one new buffer
  function mergeArrays() {
    if (arguments.length < 2) {
      throw new Error("expected at least two arrays")
    }
  
    // Determine total length
    let totalLen = 0
    for (let i = 0; i < arguments.length; i++) {
      totalLen += arguments[i].length
    }
  
    // Allocate new buffer
    const buf = new Uint8Array(totalLen)
  
    // Copy
    let offset = 0
    for (let i = 0; i < arguments.length; i++) {
      buf.set(arguments[i], offset)
      offset += arguments[i].length
    }
  
    return buf
  }
  
  /* GENERATED MESSAGE ENCODER (Person) */
  
  // 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_encode encodes a message of type "person" to a binary array
  function msgPerson_encode(obj, textEncoding) {
    // Determine the length of variable-sized string fields
    const n = textEncoding.encode(obj.name)
    const e = textEncoding.encode(obj.contact.email)
    const s = textEncoding.encode(obj.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, obj.accountValue)
  
    // Write i at 8
    view.setUint32(8, obj.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, obj.contact.address.country)
  
    return mergeArrays(
      view8.slice(0, msgPerson_minSize),
      n,
      e,
      s,
    )
  }
  
  /* GENERATED MESSAGE DECODER (Person) */
  
  // msgPerson_decode decodes a message of type "person" to an object
  function msgPerson_decode(buf, textEncoding) {
    if (buf.length < msgPerson_minSize) {
      const len = buf.length
  	  throw new Error(
  		  `message too small (${len}) to fit a person object (${msgPerson_minSize})`
  	  )
    }
    
    // Initialize buffer interfaces
    const view = new DataView(buf.buffer)
    const viewStr = textEncoding.unicode ? buf : new Uint16Array(buf.buffer)
    
    /* 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 object instance
    const obj = {
  	  accountValue: null,
  	  id: null,
  	  name: null,
  	  contact: {
  		  email: null,
  		  address: {
  			  country: null,
  			  street: null
  		  }
  	  }
    }
  
    // Read a at 0
    obj.accountValue = view.getFloat64(0)
  
    // Read i at 8
    obj.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
    obj.contact.address.country = view.getUint16(24)
  
    // Read n at 26
    obj.name = textEncoding.decode(viewStr, 26, n_l)
  
    // Read e at 26+n_l
    obj.contact.email = textEncoding.decode(viewStr, 26+n_l, e_l)
  
    // Read s at 26+n_l+e_l
    obj.contact.address.street = textEncoding.decode(viewStr, 26+n_l+e_l, s_l)
  
    return obj
  }
  
  /* 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ägenköder-Str 13" // unicode; 2+n(max:2^32-1) byte
  	  }
    }
  }
  
  /* TEST */
  
  const testTextEncodingUTF8 = new TextEncoding('utf-8')
  const testTextEncodingUTF16 = new TextEncoding('utf-16-le')
  const testTextEncodingUTF8Native = new TextEncoding('utf-8', true)
  
  const testEncodedJSON = JSON.stringify(data)
  const testEncodedBinaryUTF8 = msgPerson_encode(data, testTextEncodingUTF8)
  const testEncodedBinaryUTF16 = msgPerson_encode(data, testTextEncodingUTF16)
  

};
</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 UTF-8
msgPerson_encode(data, testTextEncodingUTF8)
pending…
Binary Encode UTF-8 (Native)
msgPerson_encode(data, testTextEncodingUTF8Native)
pending…
Binary Decode UTF-8
msgPerson_decode(testEncodedBinaryUTF8, testTextEncodingUTF8)
pending…
Binary Decode UTF-8 (Native)
msgPerson_decode(testEncodedBinaryUTF8, testTextEncodingUTF8Native)
pending…
Binary Encode UTF-16
msgPerson_encode(data, testTextEncodingUTF16)
pending…
Binary Decode UTF-16
msgPerson_decode(testEncodedBinaryUTF16, testTextEncodingUTF16)
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