はじめに
今回は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