25
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LZ4 ブロック圧縮 API の使い方

Last updated at Posted at 2014-05-01

高速な圧縮ライブラリである LZ4 (作者は Yann Collet さん) のブロック API の使い方。バージョン 1.8.1.2 を対象としています。

LZ4 のプロトタイプ宣言は lz4.h から引用しています。

LZ4 ブロック圧縮 API

ここでいう圧縮・伸長処理は、LZ4 フレームフォーマット (lz4io) のこと 1 ではなく、LZ4 ブロックフォーマット の事です。

圧縮データブロックは、自身の大きさや終端情報を持たないため、利用者側で管理する必要があります。

圧縮処理

lz4 に関する前処理や後処理を必要としないため、とても簡単に扱えます。

処理に必要な関数は一つだけです。zlib の compress() 関数に相当します。

(lz4.h から引用)

LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);

サンプルコード

const char *src = ...;
char *dest = ...;
int srcsize = ...;
int destsize = ...;

destsize = LZ4_compress_default(src, dest, srcsize, destsize);
if (destsize == 0) {
  圧縮に失敗しました。
  srcsize  LZ4_MAX_INPUT_SIZE を超えているか、destsize が不足しています。
} else {
  圧縮できました。
  dest に圧縮後のデータが、destsize に圧縮後のデータサイズが入っています。
}

圧縮後のサイズ (最悪値) を入力サイズから求める

destsizesrcsize から最悪値を求める事が出来ます。LZ4 はそのための関数を提供しています。

(lz4.h から引用)

LZ4LIB_API int LZ4_compressBound(int inputSize);

サンプルコード

int destsize = LZ4_compressBound(srcsize);

さらに速く!

LZ4_compress_default() の圧縮率を抑えてさらに速く処理するための関数が用意されています。

(lz4.h から引用)

LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);

NOTE:

  • acceleration 引数は 1 以上の整数値が有効なものとして扱います。0 以下の値は 1 と同じ意味となります。
  • LZ4_compress_default()acceleration 引数に 1 を渡した場合と等価です。

伸長処理

圧縮処理と同様、扱いは簡単。

zlib の uncompress() 関数に相当します。

(lz4.h から引用)

lz4.h
LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);

サンプルコード

const char *src = ...; /* LZ4 ブロックフォーマットで圧縮されたデータ */
int srcsize = ...;
char *dest = ...;
int destsize = ...;

destsize = LZ4_decompress_safe(src, dest, srcsize, destsize);
if (destsize < 0) {
  伸長に失敗しました。
  理由としては src データに間違いがある、あるいは destsize が不足している、などです。
  LZ4 はエラーの原因を特定しません。
} else {
  伸長に成功しました。
  dest に伸長後のデータが、destsize に伸長データのサイズが入っています。
}

最初の位置から指定されたサイズだけを伸長する (部分的な伸長)

LZ4 は最初の位置から部分的な範囲を伸長させる機能を提供しています。

先頭部分のみの伸長を必要とする場合に便利です。

(lz4.h から引用)

lz4.h
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);

戻り値が 0 未満であれば伸長に失敗しています。

LZ4 ブロックストリーム圧縮 API

ストリームとなっていますが、先に述べた LZ4_compress_default()ストリーム圧縮版というわけではありません
プリセット辞書を与えて LZ4_compress_default() で連続した圧縮処理を行うことだと考えるほうがわかりやすいと思います。

伸長処理も同様に、プリセット辞書付きの LZ4_decompress_safe() と考えるといいかもしれません。

ここではプリセット辞書のようなもののことを LZ4 でも dict としているのに倣って dict と呼ぶことにします。

圧縮処理

LZ4_compress_default() とは異なり、少々面倒です。

(lz4.h から引用)

typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */

LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
LZ4LIB_API int           LZ4_freeStream (LZ4_stream_t* streamPtr);
LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int dictSize);

最低限となる処理の流れは次のとおりです。

  1. lz4 ブロックストリーム圧縮器の初期化 (LZ4_createStream())
  2. 圧縮処理 (LZ4_compress_fast_continue())
  3. lz4 ブロックストリーム圧縮器の破棄 (LZ4_freeStream())

ただし連続して圧縮する時、LZ4_compress_fast_continue() 関数の src 引数に渡すバッファ内容が前回の内容を維持できない場合は、利用者側で dict バッファを確保して 2. で呼ぶ LZ4_compress_fast_continue() のあとに LZ4_saveDict() を呼ぶ必要があります。

1. lz4 ブロックストリーム圧縮器の初期化

LZ4_createStream() 関数を呼ぶだけです。

LZ4_stream_t *lz4s = LZ4_createStream();

必要であれば dict バッファの確保もしておくといいでしょう。

size_t dictsize = 65536; /* 必要に応じて変動させて下さい。64 KiB を越えた領域は使われません。 */
char *dictbuf = malloc(dictsize);

辞書を事前に与える

予め辞書を用意しておいて利用したい場合、LZ4_createStream() を呼んだ後で LZ4_loadDict() を使います。

const char *predict = ...;
int predictsize = ...;

int dictsize = LZ4_loadDict(lz4s, predict, predictsize);

この時与えた predict バッファの内容が保持できない・保持できるかわからない場合は LZ4_savaDict() を呼んで下さい。

2. 圧縮処理

LZ4_compress_fast_continue() を用いてデータを圧縮します。

const char *src1 = ...;
int src1size = ...;
const char *src2 = ...;
int src2size = ...;
const char *src3 = ...;
int src3size = ...;
const char *src4 = ...;
int src4size = ...;

char *dest = ...;
int destsize = ...;
int newdestsize = ...;

newdestsize = LZ4_compress_fast_continue(lz4s, src1, dest, src1size, destsize, 0);
if (newdestsize == 0) { ...エラー処理... }
report_for_compressed(dest, newdestsize);

newdestsize = LZ4_compress_fast_continue(lz4s, src2, dest, src2size, destsize, 0);
if (newdestsize == 0) { ...エラー処理... }
report_for_compressed(dest, newdestsize);

newdestsize = LZ4_compress_fast_continue(lz4s, src3, dest, src3size, destsize, 0);
if (newdestsize == 0) { ...エラー処理... }
report_for_compressed(dest, newdestsize);

newdestsize = LZ4_compress_fast_continue(lz4s, src4, dest, src4size, destsize, 0);
if (newdestsize == 0) { ...エラー処理... }
report_for_compressed(dest, newdestsize);

...

入力データを変動させる場合は LZ4_compress_fast_continue() する度に、用意した dict バッファを LZ4_saveDict() 関数に与えます。

const char *src = ...;
int srcsize = ...;
char *dest = ...;
int destsize = ...;

while (必要なだけ繰り返す) {
  入力データの取り込み(&src, &srcsize);
  int newdestsize = LZ4_compress_fast_continue(lz4s, src, dest, srcsize, destsize);
  if (newdestsize == 0) {
    ...LZ4_compress_fast_continue() に失敗しました...
  }
  report_for_compressed(dest, newdestsize);

  int newdictsize = LZ4_saveDict(lz4s, dict, dictsize);
  if (newdictsize < 0) {
    ...LZ4_saveDict() に失敗しました...
  } else {
    成功しました。dict に更新されたデータが、newdictsize に更新されたデータ長が入っています。

    dictsize = newdictsize;
  }
}

LZ4_saveDict() の直後に LZ4_loadDict() を呼び出すことは不要だと説明がありますが、続く LZ4_compress_fast_continue() が失敗することがある場合は LZ4_saveDict() の後ろに LZ4_loadDict() を置くと成功するかもしれません。

3. lz4 ブロックストリーム圧縮器の破棄

LZ4_freeStream() を使って圧縮コンテキストを破棄します。

LZ4_freeStream(lz4s);

dict バッファも不要なので、確保してあるのであれば開放したり別の処理で再利用したり出来ます。

あとは自前で用意したものがあればそれらの後片付けをするのがいいでしょう。

ブロックストリーム伸長処理

LZ4_decompress_safe_continue() を使う方法と LZ4_decompress_safe_usingDict() を使う方法があります。

この文書では LZ4_decompress_safe_usingDict() を用いて、多少の効率を落としながらも安全に利用できる方向で記述しています 2

LZ4_decompress_safe_continue() を使いたい場合のために 使う際のヒント を記してあります。

LZ4_decompress_safe_usingDict() を用いる場合に使う API は、関数ひとつだけで済みます。

(lz4.h から引用)

LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize);

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

  1. dict バッファの確保
  2. 伸長処理 (LZ4_decompress_safe_usingDict())
  3. dict バッファの開放

1. dict バッファの確保

dict バッファが必要なため、ヒープから確保します。

int dict_capacity = 64 * 1024; /* 64 KiB の確保 */
char *dict = malloc(dict_capacity);
int dictsize = 0;

バッファサイズは通常であれば 64 KiB (65536 バイト) 必要です。
これを下回ると、伸長処理が失敗する可能性があります。特筆するべき理由がない限り 64 KiB にしておくのが無難です。
逆にこれ以上確保しても無駄になるだけとなります。

プリセット辞書が存在する場合、dict バッファに内容をコピーしておきます。

const char *predict = ...;
int predictsize = ...;

if (predictsize > dict_capacity) {
  predict = predict + predictsize - dict_capacity;
  predictsize = dict_capacity;
}

memcpy(dict, predict, predictsize);
dictsize = predictsize;

2. 伸長処理

伸長処理では LZ4_decompress_safe_usingDict() と dict バッファの更新を行います。

while (必要なだけ繰り返す) {
  const char *src = ...;
  int srcsize = ...;
  char *dest = ...;
  int dest_capacity = ...;

  int destsize = LZ4_decompress_safe_usingDict(src, dest, srcsize, dest_capacity, dict, dictsize);

  if (destsize < 0) {
    伸長処理に失敗しました。
  }

  伸長処理に成功しました。dest に伸長した出力データが、destsize に出力データ長が入っています。

  /* dict バッファの更新 */

  if (destsize >= dict_capacity) {
    memcpy(dict, dest + destsize - dict_capacity, dict_capacity);
    dictsize = dict_capacity;
  } else {
    if (destsize > (dict_capacity - dictsize)) {
      int keep_size = dict_capacity - destsize;

      memmove(dict, dict + dictsize - keep_size, keep_size);
      dictsize = keep_size;
    }

    memcpy(dict + dictsize, dest, destsize);
    dictsize += destsize;
  }
}

3. dict バッファの開放

dict バッファを開放するだけです。

free(dict);

LZ4_decompress_safe_continue を使う際のヒント

LZ4_decompress_safe_continue() を使って効率的な伸長処理を行いたい場合は、次の点に注意しなければ出力データの破壊につながります。

  • LZ4_setStreamDecode()

    プリセット辞書を与える場合は、その辞書バッファの内容を維持する必要があります。
  • LZ4_decompress_safe_continue()

    初回時以降は、前回の出力バッファの内容 3 が保持されているか、同じ出力バッファであれば前回の出力バッファが少なくとも64 KiB + 数十バイト程度埋められているべきです。

(lz4.h から引用)

typedef union LZ4_streamDecode_u LZ4_streamDecode_t;   /* incomplete type (defined later) */

LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
LZ4LIB_API int                 LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);

LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);

LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity);

役には立たないおまけ技

  • 初回のプリセット辞書を伴うことなく LZ4_compress_fast_continue() で圧縮されたデータは、dict が不要なため LZ4_decompress_safe() で正常に伸長することができます。
    2回目以降の圧縮データであっても、dict に依存していなければ伸長可能です。
  • LZ4_compress_fast() で圧縮されたデータは、LZ4_decompress_continue() / LZ4_decompress_safe_usingDict() で伸長することができます。

以前の文書との差異について

以前の文書に書かれた殆どの関数は lz4-1.8.1.2 のずっと前から DEPRECATED (廃止予定) となっている 4 ため、利用する関数を差し替えています。

以前の文書で使っていて廃止予定・非推奨となったため置き換えた関数を並べてみます。

  • LZ4_compress() (DEPRECATED) → LZ4_compress_default() を使う
  • LZ4_compress_limitedOutput() (DEPRECATED) → LZ4_compress_default() を使う
  • LZ4_create() (DEPRECATED) → LZ4_createStream() を使う
  • LZ4_free() (DEPRECATED) → LZ4_freeStream() を使う
  • LZ4_compress_continue() (DEPRECATED) → LZ4_compress_fast_continue() を使う
  • LZ4_slideInputBuffer() (DEPRECATED) → LZ4_saveDict() を使う
  • LZ4_decompress_safe_withPrefix64k() (DEPRECATED) → LZ4_decompress_safe_usingDict() を使う

この文書について

CC0 この文書は引用されたものを除き クリエイティブ・コモンズ ゼロ ライセンスの下に提供されています。

  1. LZ4 フレームフォーマットは lz4 コマンドラインプログラム (lz4-cli) で扱う事が出来ます。ブロックフォーマットは扱えません。

  2. LZ4_decompress_safe_continue() でも (多少効率の低下がありながらも) 安全に利用できる方法がありますが、やることがほとんど変わらない上に LZ4_decompress_safe_usingDict() よりも若干の手間がかかるので割愛します。

  3. 正確にはバッファ末端の最大64 KiB の部分です。

  4. LZ4 でこれらの関数が OBSOLUTE (非推奨) になったのは2014年6月26日にリリースされたバージョン r118 からです。開発版においては、この文書を以前投稿した 19日後 (2014年5月20日) です。

25
23
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
25
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?