C#,Utf8の定数の扱い方(パフォーマンス大事)

C#のUtf8の扱い方をいろいろまとめてみる。

まず、前提として・・・
string(System.String)は、Utf16の変更不可オブジェクト。
私がAPIでよく使うJsonは、Utf8。

例えば、APIでオーダーリクエストを投げる時に、
Buy,Sell,価格,量とかをJsonで投げる場合を想定して、
stringの”Buy”を、Json(Utf8)に変換すると、当然、
Utf16→Utf8への変換が都度発生し、その分遅くなる。

そこで、Utf8を定数として記述できれば都合が良いのだけれど、
C#10の現在では、まだない。
今使える、方法どんなものがあるのか。

1.
stringを、都度byte[]に変換。

byte[] bytes = Encoding.UTF8.GetBytes("Buy");

速度が重要な場所で無ければ、扱いやすいし問題は無いかと。
速度が大事な時は、Utf16→Utf8の変換が入るので避けたいところ。

2.
classに、static で、byte[]を作っておく。

public static readonly byte[] b_buy = Encoding.UTF8.GetBytes("Buy");

初期化に時間が少しかかるのは仕方がないとして、
繰り返しで使う場合には、速度が出る。

3.
メソッドの中で、ReadOnlySpanで定義して使う。

ReadOnlySpan<byte> bytes = new byte[] { (byte)'B', (byte)'u', (byte)'y'};

最適化がかかる様で、byte[]をヒープメモリに作って、GCに負担がかかることはないらしい。
https://ufcpp.net/blog/2021/12/utf8-literal/

ベンチマークで計測してみたところ、(2.)(3.)はほぼ同じ速度が出ました。
(3.)の方が速いかと思ったので、ちょっと意外でした。
ReadOnlySpanなので、classのメンバーにできないとか、制限もあるので、全部(3.)でというわけにいかないのが悩ましいところ。

4.
シリアライザに、string(“Buy”など)を変換してもらう。
当然、(1.)と同じく遅い要因になる。

私がシリアライズする際は、Utf8Jsonを使っていますが、
byte[]を、Json文字に変換してくれる様に、writerの拡張メソッドを書いて、
Formatterを、属性で指定して使っています。

public static void WriteUtf8Bytes(ref this JsonWriter writer, byte[] value)
{
    writer.EnsureCapacity(value.Length + 2);
    writer.WriteRawUnsafe((byte)'"');
    for (int i = 0; i < value.Length; i++)
    {
        writer.WriteRawUnsafe(value[i]);
    }
    writer.WriteRawUnsafe((byte)'"');
}

public sealed class Utf8BytesFormatter : IJsonFormatter<byte[]>
{
    public static readonly Utf8BytesFormatter Default = new();

    public void Serialize(ref JsonWriter writer, byte[] value, IJsonFormatterResolver formatterResolver)
    {
        writer.WriteUtf8Bytes(value);
    }

    public byte[] Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        return reader.ReadUtf8Bytes();
    }
}

public readonly struct OrderParamStruct
{
    [JsonFormatter(typeof(Utf8BytesFormatter))]
    public readonly byte[] side;

    // 略
}

public static readonly byte[] b_BUY = Encoding.UTF8.GetBytes("BUY");

var orderParamObject = new OrderParamStruct(Utf8Lib.b_BTC_JPY, Utf8Lib.b_LIMIT, Utf8Lib.b_BUY, price, amount);
byte[] paramJson = JsonSerializer.Serialize(orderParamObject);

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です