C# 10.0 補間文字列のパフォーマンス改善

2021/9/14に.NET 6 RC1(Release Candidate = リリース候補版)がリリースされました。.NET 6 には多くの新機能が含まれています。今年の11月には正式版がリリースされる予定で待ち遠しいところですが、今回はRC1でC# 10.0 の新機能 補間文字列(interpolated string)のパフォーマンス改善 に注目してみました。

変更点

例えば以下のようなコードがあったとします。

string value = $"{a}:{b}";

このコードの展開結果が以下のように変更されました。

// C# 9.0 まで
string value = string.Format("{0}:{1}", a, b);

// C# 10.0
DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(1, 2);
handler.AppendFormatted(a);
handler.AppendLiteral(":");
handler.AppendFormatted(b);
string s = handler.ToStringAndClear();

string.Format が使用されなくなるため、以下のようなコストが削減できるそうです。

  • params による new object[] のコスト
  • 値型を渡したときのボックス化のコスト

補間文字列の機能については、こちらでより詳しく解説されています。

ベンチマーク

string.Format、補完文字列、および文字列の構築でよく使われるStringBuilder を、補完される文字列数をパラメータとして以下のようなベンチマークを作成し、パフォーマンスを測定しました。(1 : $"{a}" 、4 : $"{a}{b}{c}{d}" というように)

private const int N = 10000;

[Benchmark]
public void StringFormat1()
{
    for (var i = 0; i < N; i++)
    {
        var str = $"i:{i}";
    }
}

結果は以下のようになりました。

paramsMeanErrorStdDevGen 0Allocated
1449.6 us8.90 us17.16 us150.8789617 KB
41,376.0 us24.09 us26.78 us513.67192,101 KB
72,370.4 us45.93 us64.39 us816.40633,343 KB
string.Format
paramsMeanErrorStdDev Gen 0Allocated
1427.2 us1.83 us1.53 us 93.2617 383 KB
4771.0 us3.71 us3.29 us150.3906616 KB
71,168.0 us22.77 us25.31 us224.6094921 KB
補間文字列
paramsMeanErrorStdDevGen 0Allocated
1220.7 us 2.20 us 1.95 us 248.5352 1,016 KB
4757.0 us5.16 us4.57 us500.97662,049 KB
71,473.3 us28.68 us28.17 us865.23443,535 KB
StringBuilder

また、こちらによるとstring.Createで初期バッファとしてSpan<char>を与えるのがパフォーマンスが良いとのことでしたので、StringBuilderで初期バッファサイズを指定したと合わせて同様に パフォーマンスを測定しました。※string.Create はループの外でstackalloc してSpan<char>を使いまわしています。

paramsMeanErrorStdDevGen 0Allocated
1227.4 us1.36 us1.27 us210.2051859 KB
4585.6 us3.86 us3.61 us477.53911,953 KB
7952.2 us12.45 us11.04 us784.17973,203 KB
StringBuilder(初期バッファ指定)
paramsMeanErrorStdDevGen 0Allocated
1233.0 us4.48 us4.60 us93.5059383 KB
4596.9 us10.62 us9.41 us150.3906616 KB
7945.2 us6.72 us6.29 us224.6094921 KB
string.Create(初期バッファ指定)

これらをグラフにすると以下のようになりました。

考察とまとめ

string.Createで初期バッファとしてSpan<char>を与えるのが最もパフォーマンスが良いというのは間違いなさそうです。ただし、バッファサイズのコントロールとunsafeを使用する必要があるため、実際に使用するのは何度も呼ばれるメソッドや性能要件が厳しいケースに限定されそうです。

補間文字列とStringBuilder (初期バッファ指定なし)でパラメータ数によってMeanの順位が入れ替わっているのは、StringBuilderDefaultCapacityを越えたときのバッファ再確保が影響していると思われます。補間文字列の方がメモリ割り当てがかなり少ないため、ガベージコレクションまで考慮するととりあえず補間文字列を使っておけば良いと思われます。

今回の検証結果から、社内のコーディングルールに以下を追加します。

  • string.Formatを使わず補間文字列を使う
  • 補間文字列だけでロジックを構築できないときはStringBuilderを使う
  • 性能要件が厳しいときのみstring.Createの使用を検討する

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA