はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity, C#】K4os.Compression.LZ4を使ってLZ4の圧縮・展開をしてみる

はじめに

今回はUnityでLZ4を用いてバイト配列を圧縮してみるという記事になります。

LZ4 は圧縮と展開の速さに焦点を当てた可逆圧縮アルゴリズムである。バイト指向の圧縮方法であるLZ77ファミリーに属する。

gzipのようなアルゴリズムより低い圧縮率であるLZOよりわずかに圧縮率が低い。しかし、圧縮速度はLZOと同等であり、gzipより数倍速い。展開速度はLZOより著しく速くなりうる[2]。

LZ4 - Wikipedia

ライブラリ

.NETで動作するライブラリをNuGetを探してみたところ、K4os.Compression.LZ4が一番利用されているようでした。
github.com

確認してみたところしっかりと更新されているし、ドキュメントもしっかりしているようですし利用させていただきます。

インストール

Unityで利用するには、依存先のライブラリも含めて以下が必要になります。

NuGetからインストールする手法はお任せします。

手法の一つとして一番シンプルなのは、ブラウザからDownload packageをダウンロード、nupkg -> zipにして展開、lib/netstandard2.0/〇〇.dllをUnityのPluginsフォルダに入れればOKです。

Download package

利用方法

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.ReadAllBytes24msだったので、多少は増えていますがかなり早いと思います。
(厳密な比較はできないが、LZMAで圧縮された25.1MBAssetBundleのファイル読み込み・展開は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