btoa vs third party base64 encoder

JavaScript performance comparison

Revision 12 of this test case created

Info

Encoding large data in base64 can be slow. Also, native implementations of btoa do not correctly handle utf-8 characters. Searching for the best performing alternative.

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    /**
     *
     *  Base64 encode / decode
     *  http://www.webtoolkit.info/
     *
     **/

   
    Base64 = {
   
      // private property
      _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
   
      // public method for encoding
      encode: function(input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
   
        input = Base64._utf8_encode(input);
   
        while (i < input.length) {
   
          chr1 = input.charCodeAt(i++);
          chr2 = input.charCodeAt(i++);
          chr3 = input.charCodeAt(i++);
   
          enc1 = chr1 >> 2;
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
          enc4 = chr3 & 63;
   
          if (isNaN(chr2)) {
            enc3 = enc4 = 64;
          } else if (isNaN(chr3)) {
            enc4 = 64;
          }
   
          output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
   
        }
   
        return output;
      },
   
      // public method for decoding
      decode: function(input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
   
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
   
        while (i < input.length) {
   
          enc1 = this._keyStr.indexOf(input.charAt(i++));
          enc2 = this._keyStr.indexOf(input.charAt(i++));
          enc3 = this._keyStr.indexOf(input.charAt(i++));
          enc4 = this._keyStr.indexOf(input.charAt(i++));
   
          chr1 = (enc1 << 2) | (enc2 >> 4);
          chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
          chr3 = ((enc3 & 3) << 6) | enc4;
   
          output = output + String.fromCharCode(chr1);
   
          if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
          }
          if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
          }
   
        }
   
        output = Base64._utf8_decode(output);
   
        return output;
   
      },
   
      // private method for UTF-8 encoding
      _utf8_encode: function(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
   
        for (var n = 0; n < string.length; n++) {
   
          var c = string.charCodeAt(n);
   
          if (c < 128) {
            utftext += String.fromCharCode(c);
          } else if ((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
          } else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
          }
   
        }
   
        return utftext;
      },
   
      // private method for UTF-8 decoding
      _utf8_decode: function(utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;
   
        while (i < utftext.length) {
   
          c = utftext.charCodeAt(i);
   
          if (c < 128) {
            string += String.fromCharCode(c);
            i++;
          } else if ((c > 191) && (c < 224)) {
            c2 = utftext.charCodeAt(i + 1);
            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
            i += 2;
          } else {
            c2 = utftext.charCodeAt(i + 1);
            c3 = utftext.charCodeAt(i + 2);
            string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
            i += 3;
          }
   
        }
   
        return string;
      }
   
    }
   
    chr = function(code) {
      return String.fromCharCode(code);
    };
   
    //returns utf8 encoded charachter of a unicode value.
    //code must be a number indicating the Unicode value.
    //returned value is a string between 1 and 4 charachters.
    code2utf = function(code) {
      if (code < 128) return chr(code);
      if (code < 2048) return chr(192 + (code >> 6)) + chr(128 + (code & 63));
      if (code < 65536) return chr(224 + (code >> 12)) + chr(128 + ((code >> 6) & 63)) + chr(128 + (code & 63));
      if (code < 2097152) return chr(240 + (code >> 18)) + chr(128 + ((code >> 12) & 63)) + chr(128 + ((code >> 6) & 63)) + chr(128 + (code & 63));
    };
   
    //it is a private function for internal use in utf8Encode function
    _utf8Encode = function(str) {
      var utf8str = new Array();
      for (var i = 0; i < str.length; i++) {
        utf8str[i] = code2utf(str.charCodeAt(i));
      }
      return utf8str.join('');
    };
   
    //Encodes a unicode string to UTF8 format.
    utf8Encode = function(str) {
      var utf8str = new Array();
      var pos, j = 0;
      var tmpStr = '';
   
      while ((pos = str.search(/[^\x00-\x7F]/)) != -1) {
        tmpStr = str.match(/([^\x00-\x7F]+[\x00-\x7F]{0,10})+/)[0];
        utf8str[j++] = str.substr(0, pos);
        utf8str[j++] = _utf8Encode(tmpStr);
        str = str.substr(pos + tmpStr.length);
      }
   
      utf8str[j++] = str;
      return utf8str.join('');
    };
   
    function encodeBase64(str) {
      var chr1, chr2, chr3, rez = '',
          arr = [],
          i = 0,
          j = 0,
          code = 0;
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('');
   
      while (code = str.charCodeAt(j++)) {
        if (code < 128) {
          arr[arr.length] = code;
        } else if (code < 2048) {
          arr[arr.length] = 192 | (code >> 6);
          arr[arr.length] = 128 | (code & 63);
        } else if (code < 65536) {
          arr[arr.length] = 224 | (code >> 12);
          arr[arr.length] = 128 | ((code >> 6) & 63);
          arr[arr.length] = 128 | (code & 63);
        } else {
          arr[arr.length] = 240 | (code >> 18);
          arr[arr.length] = 128 | ((code >> 12) & 63);
          arr[arr.length] = 128 | ((code >> 6) & 63);
          arr[arr.length] = 128 | (code & 63);
        }
      };
   
      while (i < arr.length) {
        chr1 = arr[i++];
        chr2 = arr[i++];
        chr3 = arr[i++];
   
        rez += chars[chr1 >> 2];
        rez += chars[((chr1 & 3) << 4) | (chr2 >> 4)];
        rez += chars[chr2 === undefined ? 64 : ((chr2 & 15) << 2) | (chr3 >> 6)];
        rez += chars[chr3 === undefined ? 64 : chr3 & 63];
      };
      return rez;
    };
   
    // based on Kevin van Zonneveld (http://kevin.vanzonneveld.net), original by Webtoolkit.info (http://www.webtoolkit.info/)
    // improved by: sowberry, Yves Sucaet, kirilloid
    // bugfixed by: Onno Marsman, Onno Marsman, Ulrich, Rafal Kukawski
    // tweaked by: Jack
   
    function utf8_encode(a) {
      var d = 0,
          b = 0,
          c = "",
          f, e;
      if (null === a || "undefined" === typeof a) {
        return ""
      }
      a += "";
      f = a.length;
      for (var g = 0; g < f; g++) {
        e = a.charCodeAt(g), crt_encode = null, 128 > e ? d++ : crt_encode = 127 < e && 2048 > e ? String.fromCharCode(e >> 6 | 192, e & 63 | 128) : String.fromCharCode(e >> 12 | 224, e >> 6 & 63 | 128, e & 63 | 128), null !== crt_encode && (d > b && (c += a.slice(b, d)), c += crt_encode, b = d = g + 1)
      }
      d > b && (c += a.slice(b, f));
      return c
    }
    // based on http://www.webtoolkit.info/
   
    function base64_encode(a) {
      var d = "",
          b, c, f, e, g, h, j = 0,
          k;
      a = utf8_encode(a);
      for (k = a.length; j < k;) {
        b = a.charCodeAt(j++), c = a.charCodeAt(j++), f = a.charCodeAt(j++), e = b >> 2, b = (b & 3) << 4 | c >> 4, g = (c & 15) << 2 | f >> 6, h = f & 63, isNaN(c) ? g = h = 64 : isNaN(f) && (h = 64), d += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(e) + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(b) + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(g) + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)
      }
      return d
    }
};
</script>

Test runner

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

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Native btoa function on small image
b = btoa(a);
pending…
Native btoa function on large video
b = btoa(vid);
pending…
native btoa with native utf-8 encoding on small image
b = btoa(unescape(encodeURIComponent(a)));
pending…
native btoa with native utf-8 encoding on large video
b = btoa(unescape(encodeURIComponent(vid)));
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment