1
0

More than 1 year has passed since last update.

【JavaScript】Number型の配列をFloat32Arrayへ変換して、バイナリのままbase64で保存【データ圧縮】

Last updated at Posted at 2020-03-06

はじめに

乱数をn個発生させ、それを配列で保持します。
その配列を文字列することに興味があるとします。
そのままJSON文字列にすると、配列のサイズにもよりますが、中々のサイズになります。
もし、多少精度が落ちてもいいのなら、まあまあ圧縮できます。
Number型は、8バイトですので、Float32Arrayに変換するだけでも、サイズが半分になります。
4バイトでも十分なケースって結構多いんじゃないでしょうか。
そのFloat32Arrayをバイナリのまま、base64にします。
要は、以下の2つのパターンで比較をしたいってことです。
無題.png

圧縮率の理論値

圧縮率を「削減された量の元の量に対する割合」とします。
ざっくり計算してみます。
そのままjson文字列にすると、今回発生させる乱数は0から1なので、
0と小数点とカンマで3バイト、有効桁数が16桁、合計で19文字ですので、
約19*nバイトになります。
一方、Float32Arrayをバイナリのままbase64にした場合、
Float32Arrayは4バイトで、base64にする時に、約4/3倍になりますので
合計で約(4 * 4 / 3)*nバイトとなります。
圧縮率は16/3÷19= 0.28070175438=28%
となり、まあまあ圧縮できます。

プログラムで確認(ログ出力)

Number型配列→JSON文字列→Number型配列・・・①
Number型配列→Float32Array→base64→Float32Array→Number型配列・・・②
①と②を比較すると、大体以下のようになります。
文字列のサイズ ①:②=100:28
Number型配列から文字列にするまでの処理時間 ①:②=1:2
文字列からNumber型配列にするまでの時間 ①:②=5:1
全体の処理時間 ①:②=1:2

乱数を100000個発生させた場合のログ

100000.png

乱数を500000個発生させた場合のログ

500000.png

乱数を1000000個発生させた場合のログ

1000000.png

Float32Array→base64が遅い

Float32Arrayをbase64にするところですが、
配列の長さが大きくなると発生するmaximum call stack size exceededを避けるために
reduceでループ処理しているのですが、それが遅いのかなーって思っています。
こちらを参考にして作成しました。

よい方法があれば教えてください。
マルチスレッドは使えそうですねー。

ソース

※プログラムの動作確認は、PC版のChromeで行いました。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>float32array sample</title>
<script>
let startTime, endTime;

// 乱数の配列を作成する
const array = [];
for(let i = 0, len = 500000; i < len; i += 1) {
    array.push(Math.random());
}

// Number型配列をJSON文字列に変換
startTime = new Date();
const json = JSON.stringify(array);
endTime = new Date();
const arrayToJsonTime = endTime - startTime;

// JSON文字列をNumber型配列に変換
startTime = new Date();
const parsedArray = JSON.parse(json);
endTime = new Date();
const jsonToArrayTime = endTime - startTime;

// 配列をFloat32Arrayへ変換
startTime = new Date();
const float32Array = arrayToFloat32Array(array);
endTime = new Date();
const arrayToFloat32ArrayTime = endTime - startTime;

// Float32Arrayをbase64へ変換
startTime = new Date();
const base64 = float32ArrayToBase64(float32Array);
endTime = new Date();
const float32ArrayToBase64Time = endTime - startTime;

// base64をFloat32Arrayへ変換
startTime = new Date();
const loadedFloat32Array = base64ToFloat32Array(base64);
endTime = new Date();
const base64ToFloat32ArrayTime = endTime - startTime;

startTime = new Date();
const loadedArray = float32ArrayToArray(loadedFloat32Array);
endTime = new Date();
const float32ArrayToArrayTime = endTime - startTime;

console.table([
    { key: '乱数の数', value: array.length },
    { key: 'Number型配列の使用メモリ(byte)', value: array.length * 8 },
    { key: 'JSON文字列のサイズ(byte)', value: json.length },
    { key: 'base64のサイズ(byte)', value: base64.length },
    { key: '圧縮率(%)', value: 100 * base64.length / json.length },
    { key: '------------------', value: '---------------------------' },
    { key: 'Number型配列 -> JSON文字列(ms)', value: arrayToJsonTime },
    { key: 'JSON文字列 -> Number型配列(ms)', value: jsonToArrayTime },
    { key: '[合算] Number型配列 -> JSON文字列 -> Number型配列(ms)', value: arrayToJsonTime + jsonToArrayTime },
    { key: '------------------', value: '---------------------------' },
    { key: 'Number型配列 -> Float32Array(ms)', value: arrayToFloat32ArrayTime },
    { key: 'Float32Array -> base64(ms)', value: float32ArrayToBase64Time },
    { key: 'base64 -> Float32Array(ms)', value: base64ToFloat32ArrayTime },
    { key: 'Float32Array -> Number型配列(ms)', value: float32ArrayToArrayTime },
    { key: '[合算] Number型配列 -> Float32Array -> base64 -> Float32Array -> Number型配列(ms)', value: arrayToFloat32ArrayTime + float32ArrayToBase64Time + base64ToFloat32ArrayTime + float32ArrayToArrayTime },
]);

function arrayToFloat32Array(array) {
    const f32 = new Float32Array(array.length);
    array.forEach(function(elm, i) {
        f32[i] = elm;
    });
    return f32;
}

function float32ArrayToArray(f32) {
    const array = [];
    for(let i = 0, len = f32.length; i < len; i += 1) {
        array.push(f32[i]);
    };
    return array;
}

function float32ArrayToBase64(f32) {
    var uint8 = new Uint8Array(f32.buffer);
    return btoa(uint8.reduce(function(data, byte) {
        return data + String.fromCharCode(byte);
    }, ''));
}

function base64ToFloat32Array(base64) {
    var binary = atob(base64),
        len = binary.length,
        bytes = new Uint8Array(len),
        i;
    for(i = 0; i < len; i += 1) {
        bytes[i] = binary.charCodeAt(i);
    }
    return new Float32Array(bytes.buffer);
}
</script>
</head>
<body>
</body>
</html>

まとめ

Number型の配列を少し精度落としてもよい(double → float)なら、
それを文字列にするとき、圧縮率28%が実現できる。
処理時間は高々2倍である。
結構有用じゃないかなって思っています。:relaxed:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0