はなちるのマイノート

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

【Unity】Unity.Collections.xxHash3.StreamingStateを利用してデータ少しずつ入力しながらハッシュ値を計算する

はじめに

前回xxHash3.Hash64xxHash3.Hash128を紹介したのですが、今回はいくつかのデータをそれぞれ計算した後にハッシュ値を取得できるxxHash3.StreamingStateを紹介したいと思います。
www.hanachiru-blog.com

// サンプルコード
// 128bits
xxHash3.StreamingState streamingState = new xxHash3.StreamingState(isHash64: false);

// 対象のデータ
NativeArray<byte> array1 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 50);
NativeArray<byte> array2 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 100);
NativeArray<byte> array3 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 200);

unsafe
{
    // ハッシュ値を計算する
    streamingState.Update(array1.GetUnsafePtr(), array1.Length);
    streamingState.Update(array2.GetUnsafePtr(), array2.Length);
    streamingState.Update(array3.GetUnsafePtr(), array3.Length);

    uint4 hash128 = streamingState.DigestHash128();

    // uint4(4123142207, 1991187312, 1036281432, 761385101)
    Debug.Log(hash128);
}

array1.Dispose();
array2.Dispose();
array3.Dispose();

概要

xxHash3.Hash64xxHash3.Hash128は引数に渡したデータからハッシュ値を計算します。

使い方としては直感的で良いのですが、例えば数GBのファイルのハッシュ値を計算したい場合を考えてください。xxHash3.Hash64xxHash3.Hash128を実行するために一度メモリにファイルを読み取らないといけないわけですが、数GBもメモリを確保してしまうとスマホなんかではメモリが足らなくなってしまいそうです。

そこで今回紹介するxxHash3.StreamingStateを利用するとこの問題に対処でき、ファイルを少し読み取った後に計算を行い、またファイルを少し読み取って計算を繰り返し、最後にハッシュ値を取り出すことができます。

↓ざっくりとした流れ

  1. xxHash3.StreamingStateを生成
  2. 「データを読み取りUpdateメソッドを実行」を繰り返す
  3. Digesthash64もしくはDigestHash128メソッドを用いて結果を取り出す
  4. xxHash3.StreamingStateが自動でリセットされる

例えば10KBのバッファを用意し、ファイルを少し読み込んでバッファに格納&Updateメソッドで計算を繰り返し、最後にDigesthash64(128)で結果を取得します。

API概要

public struct StreamingState

// Constructors
public StreamingState(bool isHash64, ulong seed = 0UL)

// Methods
public uint4 DigestHash128()
public uint2 DigestHash64()
public void Reset(bool isHash64, ulong seed = 0UL)
public void Update(void *input, int length)
public void Update<T>(in T input) where T : struct

docs.unity3d.com

詳細は公式ドキュメントを見て欲しいですが、気をつけて欲しい箇所を列挙しておきます。

  • 64bitのハッシュ値を計算するときはisHash64true128bitのハッシュ値を計算するときはfalseにする
  • DigestHash64DigestHash128を実行するとStreamingStateの状態がリセットされる

使い方

public void Start()
{
    // 128bits
    xxHash3.StreamingState streamingState = new xxHash3.StreamingState(isHash64: false);

    // ハッシュを計算するデータ
    NativeArray<byte> array1 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 50);
    NativeArray<byte> array2 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 100);
    NativeArray<byte> array3 = new NativeArray<byte>(100, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    for (var i = 0; i < array1.Length; i++) array1[i] = (byte)(i % 200);

    // パターン1
    unsafe
    {
        // ハッシュ値を計算する
        // NOTE: このサンプルだとあまり意味がないが、一括でやる場合と比較してバッファを小さくすることができる
        //       例. 「ファイルを一定サイズ読み取りUpdateを実行」を繰り返す
        streamingState.Update(array1.GetUnsafePtr(), array1.Length);
        streamingState.Update(array2.GetUnsafePtr(), array2.Length);
        streamingState.Update(array3.GetUnsafePtr(), array3.Length);

        uint4 hash128 = streamingState.DigestHash128();

        // uint4(4123142207, 1991187312, 1036281432, 761385101)
        Debug.Log(hash128);
    }

    // パターン2 : パターン1と入力したデータが同じならハッシュ値も一致する
    unsafe
    {
        NativeArray<byte> array1And2 = new NativeArray<byte>(array1.Concat(array2).ToArray(), Allocator.Temp);
        streamingState.Update(array1And2.GetUnsafePtr(), array1And2.Length);
        streamingState.Update(array3.GetUnsafePtr(), array1.Length);

        uint4 hash128 = streamingState.DigestHash128();

        // uint4(4123142207, 1991187312, 1036281432, 761385101)
        Debug.Log(hash128);

        array1And2.Dispose();
    }

    // パターン3 : パターン1と入力したデータが同じならハッシュ値も一致する
    unsafe
    {
        NativeArray<byte> array1And2And3 =
            new NativeArray<byte>(array1.Concat(array2).Concat(array3).ToArray(), Allocator.Temp);
        NativeArray<byte> arrayA = new NativeArray<byte>(array1And2And3.Slice(0, 77).ToArray(), Allocator.Temp);
        NativeArray<byte> arrayB = new NativeArray<byte>(array1And2And3.Slice(77, 100).ToArray(), Allocator.Temp);
        NativeArray<byte> arrayC =
            new NativeArray<byte>(array1And2And3.Slice(177, array1And2And3.Length - 177).ToArray(), Allocator.Temp);


        streamingState.Update(arrayA.GetUnsafePtr(), arrayA.Length);
        streamingState.Update(arrayB.GetUnsafePtr(), arrayB.Length);
        streamingState.Update(arrayC.GetUnsafePtr(), arrayC.Length);

        uint4 hash128 = streamingState.DigestHash128();

        // uint4(4123142207, 1991187312, 1036281432, 761385101)
        Debug.Log(hash128);

        array1And2And3.Dispose();
        arrayA.Dispose();
        arrayB.Dispose();
        arrayC.Dispose();
    }

    // パターン4
    unsafe
    {
        NativeArray<byte> array1And2And3 =
            new NativeArray<byte>(array1.Concat(array2).Concat(array3).ToArray(), Allocator.Temp);
        uint4 hash128 = xxHash3.Hash128(array1And2And3.GetUnsafePtr(), array1And2And3.Length);

        // uint4(4123142207, 1991187312, 1036281432, 761385101)
        Debug.Log(hash128);

        array1And2And3.Dispose();
    }

    array1.Dispose();
    array2.Dispose();
    array3.Dispose();
}