Generate B64E MessagePack that was compressed by LZ4


ASTER::EXPRESSION::JSON::Retrive the serialized binary (MessagePack) data LZ4-compressed.

ASTER で編集中の JSON を MessagePack に変換し、バイナリを Base64 でエンコードして CF25 で文字列として受け取る。

JSON はテキスト形式のデータフォーマットですが、これをバイナリ形式で扱うフォーマットが MessagePack です。

MessagePack への変換は nlohmann/jsonto_msgpack を利用しています。

関連 URL :

本関数は ASTER において編集中の JSON をメモリ上で MessagePack に変換し、次に変換したバイナリを LZ4 方式でで圧縮します。その後、圧縮されたバイナリを Base64 でエンコードし、シリアライズした結果を受け取ります。

処理の流れは以下のとおりです。

{
  "name":"ASTER",
  "version":1
}
---
title: LZ4 Data Compression & Encoding Serialization Workflow
---
graph TD;
  subgraph JSON_Data
    A1["name: 'ASTER'"]
    A2["version: 1"]
  end

  JSON_Data -->|convert to MsgPack| B[Binary Data]
  B -->|LZ4 compression| C[Base64-encoded]
  C --> |"8AaCpG5hbWWlQVNURVKndmVyc2lvbgE="| F[CF25 expression: String]
8AaCpG5hbWWlQVNURVKndmVyc2lvbgE=

Parameter. Noone


Notes.1

データサイズの観点から見た MessagePack と JSON の仕様

一般的に、圧縮を行わなくても MessagePack に変換した時点で JSON よりもデータサイズは小さくなります。

特に数値型を多く含むデータはバイナリ化の利点を活かし、効率的にデータサイズを縮小できます。

例えば nlohmann/json では数値を JSON へ保存する際のデフォルト設定があり、浮動小数点数型は全て Double 型として文字列化されます。

参考 URL
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
         std::map,
         template<typename U, typename... Args> class ArrayType = std::vector,
         class StringType = std::string, class BooleanType = bool,
         class NumberIntegerType = std::int64_t,
         class NumberUnsignedType = std::uint64_t,
         class NumberFloatType = double,// <-------------------------------------------:
         template<typename U> class AllocatorType = std::allocator,
         template<typename T, typename SFINAE = void> class JSONSerializer =
         adl_serializer,
         class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
         class CustomBaseClass = void>
class basic_json;

MessagePack はバイナリ化する際に数値データは整数・浮動小数点数など最適なデータ型で保存され、この時点でデータサイズ削減が見込めます。

JSON は "(ダブルクォート)や :(コロン)、 {}(波括弧)などデータ構造を記述するために必要な記号が最低限含まれています。MessagePack はそれらをバイナリの識別子やタグでの管理に置換することで冗長な記述を削減できます。


Notes.2

データサイズと Base64 エンコード

MessagePack へ変換後にこのバイナリを圧縮すると、テキストデータを直接圧縮するより効率的です。

  • 極端にシンプルなデータは、圧縮するとデータ量が増える可能性もある。

Base64-Encode (B64E) はバイナリデータのシリアライズに広く利用されますが、データサイズは約 33 % 増加します。( Base64 が 3 バイトのバイナリデータを 4 バイトのテキスト表現に変換するため。)

  • 100 KB のバイナリデータ → B64E 後は約 133 KB

  • LZ4 で 40 % までデータを圧縮できた場合、Base64 化処理を加えてもデータサイズの縮小は達成可能。

  • Zlib 圧縮の方がデータサイズ縮小率は高いが、圧縮・解凍処理の負荷は LZ4 よりはるかに高い

Compression Method Original Size (bytes) Compressed & B64E Size (bytes)
LZ4 12,386 5,114
Zlib 12,386 3,654

現在の ASTER の設定では、B64E を使用してシリアル化された LZ4 圧縮データは、Zlib で圧縮された B64E データと比較して28.55%大きい。 このデータサイズの差は、後に Base64 の処理速度に影響を与える。


Notes.3

ログデータの解析と比較結果

テスト環境では Zlib の圧縮は LZ4 の約 20 (19.8) 倍 の時間を要する。

LZ4 は 圧縮率が低いため、圧縮後のデータサイズが Zlib との比較で大きくなる。データサイズが大きくなった分、B64E の処理時間は Zlib との比較で約 35 % 長くなった

Base64 デコード (B64D) もデータサイズの影響を受け、LZ4 で圧縮されたデータは約 42.68 % 長い処理時間を要した。

しかしその後の解凍処理では LZ4 が Zlib よりも 約 3 倍速い。Base64 処理がボトルネックだが、全体として LZ4 を使った処理は Zlib より断然に高速であると結論づけられる。


処理速度比較: 圧縮・解凍 B64E・B64D

対象データサイズ 12,386 bytes
  • LZ4 + B64E : データサイズ = 5,114 bytes (Zlib+B64E より 28.55 % 大きい)
  • Zlib + B64E : データサイズ = 3,654 bytes
処理方法 平均処理時間 (ms) 比較速度
LZ4 Compression 11.57 ~20x faster vs. Zlib
LZ4 Decompression 9.53 ~3x faster vs. Zlib
Zlib Compression 232.70 -
Zlib Decompression 32.88 -
B64E: LZ4 Cmp 9.42 34.57% drop in speed (Zlib)
B64D: LZ4 Cmp 10.30 42.68% drop in speed (Zlib)
B64E: Zlib Cmp 7.00 -
B64D: Zlib Cmp 7.22 -
LZ4 Cmp + B64E 20.99 ~11x faster vs. Zlib
LZ4 Dcmp + B64D 19.83 ~2x faster vs. Zlib
Zlib Cmp + B64E 239.70 -
Zlib Dcmp + B64D 40.10 -

まとめ

Base64 の処理時間はデータサイズに比例して増加。LZ4 圧縮データでは Base64 がボトルネック。

  • Base64 は独自に最適化済み。SIMD 命令の導入以外で処理速度向上は見込み薄。
  • LZ4 圧縮は圧倒的に高速だが、Base64 処理の影響で速度差が縮まる。
  • Zlib 圧縮には LZ4 圧縮の 20 倍の処理時間 が必要。
  • Zlibが極端に遅いわけではなく、その有用性は用途による。