はじめに
今回はUnityでLZ4を用いてバイト配列を圧縮してみるという記事になります。
LZ4 は圧縮と展開の速さに焦点を当てた可逆圧縮アルゴリズムである。バイト指向の圧縮方法であるLZ77ファミリーに属する。
gzipのようなアルゴリズムより低い圧縮率であるLZOよりわずかに圧縮率が低い。しかし、圧縮速度はLZOと同等であり、gzipより数倍速い。展開速度はLZOより著しく速くなりうる[2]。
ライブラリ
.NETで動作するライブラリをNuGetを探してみたところ、K4os.Compression.LZ4が一番利用されているようでした。
github.com
確認してみたところしっかりと更新されているし、ドキュメントもしっかりしているようですし利用させていただきます。
インストール
Unityで利用するには、依存先のライブラリも含めて以下が必要になります。
- K4os.Compression.LZ4
- K4os.Compression.LZ4.Streams
- K4os.Hash.xxHash
- System.Runtime.CompilerServices.Unsafe
NuGetからインストールする手法はお任せします。
手法の一つとして一番シンプルなのは、ブラウザからDownload packageをダウンロード、nupkg -> zipにして展開、lib/netstandard2.0/〇〇.dllをUnityのPluginsフォルダに入れればOKです。

利用方法
LZ4での圧縮・展開のサンプルコードを載せます。
using System.IO; using K4os.Compression.LZ4.Streams; public static class LZ4 { public static byte[] Compress(byte[] buffer) { using (MemoryStream ms = new MemoryStream()) { using (var stream = LZ4Stream.Encode(ms)) { stream.Write(buffer, 0, buffer.Length); } return ms.ToArray(); } } public static byte[] Decompress(byte[] buffer) { using (MemoryStream ms = new MemoryStream(buffer)) { using (var source = LZ4Stream.Decode(ms)) { using (var target = new MemoryStream()) { source.CopyTo(target); return target.ToArray(); } } } } public static byte[] Decompress(string filePath) { using (var source = LZ4Stream.Decode(File.OpenRead(filePath))) { using (var target = new MemoryStream()) { source.CopyTo(target); return target.ToArray(); } } } }
これを利用するコードはこんな感じ。
var text = Encoding.UTF8.GetBytes("見せてもらおうか、LZ4の力というものを"); // 圧縮 var compressedText = LZ4.Compress(text); // 解凍 var decompressedText = LZ4.Decompress(compressedText); Debug.Log(Encoding.UTF8.GetString(decompressedText));
このサンプルの利用コードに書いてある文字見せてもらおうか、LZ4の力というものををLZ4圧縮したら、69 -> 54byteになっていました。
また他の例として27.2MBのバイナリファイルをファイル読み込み&展開(LZ4.Decompress(string filePath)を利用)の時間を調べてみたところ75msと爆速で動作していました。
シンプルなFile.ReadAllBytesが24msだったので、多少は増えていますがかなり早いと思います。
(厳密な比較はできないが、LZMAで圧縮された25.1MBのAssetBundleのファイル読み込み・展開は1248msかかった)
圧縮レベルの変更
ドキュメントを良くみてみると、LZ4Levelなるものがありました。
enum LZ4Level
{
L00_FAST,
L03_HC, L04_HC, L05_HC, L06_HC, L07_HC, L08_HC, L09_HC,
L10_OPT, L11_OPT, L12_MAX,
}
デフォルトではL00_FASTが設定されており、圧縮が一番早いみたいです。
ただ他のものは圧縮速度は低下しますが、処理するデータが少なくなるので早く展開できる可能性があるみたいです。
ということで実験してみました。
using (var stream = LZ4Stream.Encode(ms, LZ4Level.L03_HC))
| LZ4Level | データ容量(オリジナル: 27.3MB) |
処理速度(ファイル読み込み+展開) |
|---|---|---|
| L00_FAST | 27.2MB | 75ms |
| L03_HC | 27.2MB | 68ms |
| L10_OPT | 27.2MB | 84ms |
| L12_MAX | 27.2MB | 64ms |
ぶっちゃけ何も変わってないような気がします。
Encodeの引数で渡してあげればOKそうな気がするのですが、私が間違っているのかデータ容量・展開速度共に違いはほぼないという結果でした。
ひとこと
ドキュメントの最後の方に少し不安なコメントがありました。
Apparently ARMv7 does not handle unaligned access:
... It looks like the code here will do an unaligned memory access, which is not allowed on armv7, hence the crash. ...
どのメソッドがクラッシュしてしまうかは分かりませんが、念頭に置いておいた方が良さそうです。
Segfault in Unity 2018.3.8 compiled on Android using IL2CPP · Issue #19 · MiloszKrajewski/K4os.Compression.LZ4 · GitHub