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);