Base64 转 ArrayBuffer 效率小记

主题描述

以下三个函数功能相同,都是把 base64 转换为 ArrayBuffer
测试看看哪个效能比较好

/**
 * 把 base64 转换为 ArrayBuffer
 * 
 * @param {string} b64 要转成 ArrayBuffer 的 base64
 * @returns {ArrayBuffer}
 */
function f0(b64) {
    return (Uint8Array.from(window.atob(b64), x => x.codePointAt(0))).buffer;
}

/**
 * 功能同 f0
 * 尝试以下方向改善效能:
 * 1. 预先计算 ArrayBuffer 大小
 * 2. 用 String.prototype.codePointAt 来减少字串的处理
 */
function f1(b64) {
    let str = window.atob(b64);
    let arr = new Uint8Array(str.length);
    for(let i=arr.length;--i>=0;) {
        arr[i] = str.codePointAt(i);
    }
    return arr.buffer;
}

/**
 * 功能同 f1
 * 不使用内建的 window.atob 而是自己处理转换
 */
function f2(b64) {
    let m = (() => {
        let arr = [];
        for (let i = 25; i >= 0; --i) {
            arr[97 + i] = 26 + i;
            arr[65 + i] = i;
        }
        for (let i = 9; i >= 0; --i) {
            arr[48 + i] = 52 + i;
        }
        arr[43] = 62;
        arr[47] = 63;
        arr[61] = 0;
        return arr;
    })();
    let n = b64.length, t, k = -1, i;
    if (n < 4) {
        return null;
    }
    let emptyBtyes = b64.codePointAt(n - 2) === 61 ? 2 :
        (b64.codePointAt(n - 1) === 61 ? 1 : 0);
    let arr = new Uint8Array((n >>> 2) * 3 - emptyBtyes);
    n -= 4;
    for (i = 3; i < n; i += 4) {
        t = m[b64.codePointAt(i - 3)] << 18 |
            m[b64.codePointAt(i - 2)] << 12 |
            m[b64.codePointAt(i - 1)] << 6 |
            m[b64.codePointAt(i)];
        arr[++k] = (t >>> 16 & 255);
        arr[++k] = (t >>> 8 & 255);
        arr[++k] = (t & 255);
    }
    n += 4;
    for (; i < n; i += 4) {
        t = m[b64.codePointAt(i - 3)] << 18 |
            m[b64.codePointAt(i - 2)] << 12 |
            m[b64.codePointAt(i - 1)] << 6 |
            m[b64.codePointAt(i)];
        let len = arr.byteLength;
        if (++k < len) {
            arr[k] = t >>> 16 & 255;
        }
        if (++k < len) {
            arr[k] = t >>> 8 & 255;
        }
        if (++k < len) {
            arr[k] = t & 255;
        }
    }
    return arr.buffer;
}

测试方法

测试的程序码如下

/**
 * 比较两个 ArrayBuffer 内容是否相同
 * 
 * @param {ArrayBuffer} buf1 
 * @param {ArrayBuffer} buf2 
 * @returns {bool}
 */
function compareBuffer(buf1, buf2) {
    let arr1 = new Uint8Array(buf1);
    let arr2 = new Uint8Array(buf2);
    if (arr1.length !== arr2.length) {
        return false;
    }
    for (let i = arr1.length - 1; i >= 0; --i) {
        if (arr1[i] !== arr2[i]) {
            return false;
        }
    }
    return true;
}

/**
 * 产生指定大小的 ArrayBuffer
 * 其内容用乱数填充
 * 
 * @param {int} len 要几 bytes
 * @returns {ArrayBuffer}
 */
function genRandArray(len) {
    let arr = new Uint8Array(len);
    for (let i = 0; i < len; ++i) {
        arr[i] = Math.random() * 256 | 0;
    }
    return arr.buffer;
}

/**
 * 把 ArrayBuffer 转为 Base64
 * (这边先不考虑效能)
 * 
 * @param {ArrayBuffer} buffer 
 * @returns {String} Base64
 */
function getBase64(buffer) {
    let arr = new Uint8Array(buffer);
    let len = arr.byteLength;
    let str = '';
    for (let i = 0; i < len; ++i) {
        str += String.fromCodePoint(arr[i]);
    }
    return window.btoa(str);
}

//30 ~ 40M 大小
let origBuffer = genRandArray(30000000 + Math.random() * 10000000 | 0);
let origBase64 = getBase64(origBuffer);
let t0 = Date.now();
let buf0 = f0(origBase64);
let t1 = Date.now();
let buf1 = f1(origBase64);
let t2 = Date.now();
let buf2 = f2(origBase64);
let t3 = Date.now();
console.log(`f0: ${t1 - t0} ms ... ${compareBuffer(origBuffer, buf0)?'pass':'error'}`);
console.log(`f1: ${t2 - t1} ms ... ${compareBuffer(origBuffer, buf1)?'pass':'error'}`);
console.log(`f2: ${t3 - t2} ms ... ${compareBuffer(origBuffer, buf2)?'pass':'error'}`);

测试结果

以下是某一次测试的结果(参考)

浏览器 f0 f1 f2
firefox 1888 ms 479 ms 613 ms
chrome 3700 ms 583 ms 222 ms

结论

  • 一般状况其实不会有转换大量资料的状况
  • 万一遇到大资料,避免字串分割有关的操作可以提升效能

<<:  测试网路两端点频宽效能AB甲乙地,电脑对电脑,网路频宽,iperf jperf

>>:  【C#】Behavioral Patterns Mediator Mode

Day04 - Laravel 档案架构

昨天我们已经成功的建立一个 Laravel 专案,今天我们就来稍微了解一下 Laravel 专案下面...

【C#】Multi Value Return

这次要来学习如何让函式返回多个值~ 分别用Array~ Struct~ Tuple~ Output ...

Day7 用python写UI-聊聊标签Label方法(二)

今天要主要会介绍几个 widget 的共通方法,後面的部分会介绍如何在介面上加上图片还有其他几个实用...

Python 演算法 Day 9 - Exploratory Data Analysis

Chap.II Machine Learning 机器学习 https://yourfreetemp...

【Day 19】 实作 - 透过 AWS 服务 Glue Job 调整 Partition 以及档案格式

昨天我们已经透过 AWS Glue Crawler 自动建立 VPC Log 资料表,并且我们也看到...