はなちるのマイノート

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

【C#】BitConverter.GetBytesはbyte配列が生成されてしまうので、極力BitConverter.TryWriteBytesを利用してAllocation削減する

はじめに

今回はBitConverter.GetBytesBitConverter.TryWriteBytesに焦点をあててAllocationについて書きたいと思います。

learn.microsoft.com
learn.microsoft.com

概要

指定したデータ(例. int, ulong)をバイト配列に変換する方法としてBitConverter.GetBytesが挙げられます。

public static byte[] GetBytes (bool value);
public static byte[] GetBytes (char value);
public static byte[] GetBytes (double value);
public static byte[] GetBytes (Half value);
public static byte[] GetBytes (short value);
public static byte[] GetBytes (int value);
public static byte[] GetBytes (long value);
public static byte[] GetBytes (float value);
public static byte[] GetBytes (ushort value);
public static byte[] GetBytes (uint value);
public static byte[] GetBytes (ulong value);

BitConverter.GetBytes メソッド (System) | Microsoft Learn

// 利用例
ulong x = 10;
byte[] bytes = BitConverter.GetBytes(x);

とても直感的ではありますが大きな欠点があり、メソッドを呼び出すたびにbyte[]が生成されてしまいます。Unityで言うところのGC.Allocというやつで、マネージドヒープにメモリが確保されるのでガベージコレクションの対象になってしまうやつですね。

一度だけBitConverter.GetBytesを呼ぶならしょうがないですが、複数回呼び出されるとなるとチリツモでどうにかしたいものです。

BitConverter.TryWriteBytesを利用する

一度確保したbyte[]を使い回せば、毎回byte[]が確保されないわけですが、それを実現するためにメソッドを用意してくれています。

public static bool TryWriteBytes (Span<byte> destination, bool value);
public static bool TryWriteBytes (Span<byte> destination, char value);
public static bool TryWriteBytes (Span<byte> destination, double value);
public static bool TryWriteBytes (Span<byte> destination, Half value);
public static bool TryWriteBytes (Span<byte> destination, short value);
public static bool TryWriteBytes (Span<byte> destination, int value);
public static bool TryWriteBytes (Span<byte> destination, long value);
public static bool TryWriteBytes (Span<byte> destination, float value);
public static bool TryWriteBytes (Span<byte> destination, ushort value);
public static bool TryWriteBytes (Span<byte> destination, uint value);
public static bool TryWriteBytes (Span<byte> destination, ulong value);

BitConverter.TryWriteBytes メソッド (System) | Microsoft Learn

destinationで渡したSpanにデータを書き込んでくれるというわけです。また第2引数の型のバイト数以上にdestinationのサイズがなければfalseが返ってきます。

Spanにあまり馴染みがない人はよければこちらを一読ください。
learn.microsoft.com

利用サンプル1

使い回すようなコードを書いてみます。意味は特にないです。

private void Start()
{
    Span<byte> tmp = new byte[4].AsSpan();

    // tmpに32を書き込む
    BitConverter.TryWriteBytes(tmp, 32);
            
    // tmpに12を書き込む
    BitConverter.TryWriteBytes(tmp, 12);
            
    // tmpに51を書き込む
    BitConverter.TryWriteBytes(tmp, 51);
}


ちなみに明示的にSpanにしなくてもキャストしてくれます。

byte[] tmp = new byte[4];

// tmpに32を書き込む
BitConverter.TryWriteBytes(tmp, 32);


また配列の確保するサイズが小さいと返り値がfalseになります。

byte[] tmp = new byte[2];
            
// 32はintなので、byte[4]が必要
var flag = BitConverter.TryWriteBytes(tmp, 32);
            
// false (byte[4]ならtrue)
Debug.Log(flag);

利用サンプル2

例えばbyte[32]に対して、intを8つ連続して書き込むみたいな処理を書いてみたいと思います。

private void Start()
{
    const int byteSize = 32;
    var bytes = new byte[byteSize];
    var random = new Random();

    // random.NextBytes(bytes) のようなイメージ
    // intは4バイトなので、bytesに8つのulongを書き込んでいく
    for (var i = 0; i < byteSize / sizeof(int); i++)
    {
        var isSucceeded =
            BitConverter.TryWriteBytes(bytes.AsSpan(sizeof(int) * i, sizeof(int)), random.Next());

        // 変換が成功した場合はtrue, それ以外の場合はfalse
        Debug.Log(isSucceeded);
    }

    // 199,152,208,125,37,75,41,102,217,147,44,11,54,80,84,82,230,112,217,22,174,124,136,39,222,27,173,89,220,55,13,40
    Debug.Log(string.Join(",", bytes));
}