はじめに
乱数をn個発生させ、それを配列で保持します。
その配列を文字列することに興味があるとします。
そのままJSON文字列にすると、配列のサイズにもよりますが、中々のサイズになります。
もし、多少精度が落ちてもいいのなら、まあまあ圧縮できます。
Number型は、8バイトですので、Float32Arrayに変換するだけでも、サイズが半分になります。
4バイトでも十分なケースって結構多いんじゃないでしょうか。
そのFloat32Arrayをバイナリのまま、base64にします。
要は、以下の2つのパターンで比較をしたいってことです。
圧縮率の理論値
圧縮率を「削減された量の元の量に対する割合」とします。
ざっくり計算してみます。
そのまま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個発生させた場合のログ
乱数を500000個発生させた場合のログ
乱数を1000000個発生させた場合のログ
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倍である。
結構有用じゃないかなって思っています。