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}";
}
}
結果は以下のようになりました。
params | Mean | Error | StdDev | Gen 0 | Allocated |
1 | 449.6 us | 8.90 us | 17.16 us | 150.8789 | 617 KB |
4 | 1,376.0 us | 24.09 us | 26.78 us | 513.6719 | 2,101 KB |
7 | 2,370.4 us | 45.93 us | 64.39 us | 816.4063 | 3,343 KB |
params | Mean | Error | StdDev | Gen 0 | Allocated |
1 | 427.2 us | 1.83 us | 1.53 us | 93.2617 | 383 KB |
4 | 771.0 us | 3.71 us | 3.29 us | 150.3906 | 616 KB |
7 | 1,168.0 us | 22.77 us | 25.31 us | 224.6094 | 921 KB |
params | Mean | Error | StdDev | Gen 0 | Allocated |
1 | 220.7 us | 2.20 us | 1.95 us | 248.5352 | 1,016 KB |
4 | 757.0 us | 5.16 us | 4.57 us | 500.9766 | 2,049 KB |
7 | 1,473.3 us | 28.68 us | 28.17 us | 865.2344 | 3,535 KB |
また、こちらによるとstring.Create
で初期バッファとしてSpan<char>
を与えるのがパフォーマンスが良いとのことでしたので、StringBuilder
で初期バッファサイズを指定したと合わせて同様に パフォーマンスを測定しました。※string.Create はループの外でstackalloc
してSpan<char>
を使いまわしています。
params | Mean | Error | StdDev | Gen 0 | Allocated |
1 | 227.4 us | 1.36 us | 1.27 us | 210.2051 | 859 KB |
4 | 585.6 us | 3.86 us | 3.61 us | 477.5391 | 1,953 KB |
7 | 952.2 us | 12.45 us | 11.04 us | 784.1797 | 3,203 KB |
params | Mean | Error | StdDev | Gen 0 | Allocated |
1 | 233.0 us | 4.48 us | 4.60 us | 93.5059 | 383 KB |
4 | 596.9 us | 10.62 us | 9.41 us | 150.3906 | 616 KB |
7 | 945.2 us | 6.72 us | 6.29 us | 224.6094 | 921 KB |
これらをグラフにすると以下のようになりました。
考察とまとめ
string.Create
で初期バッファとしてSpan<char>
を与えるのが最もパフォーマンスが良いというのは間違いなさそうです。ただし、バッファサイズのコントロールとunsafe
を使用する必要があるため、実際に使用するのは何度も呼ばれるメソッドや性能要件が厳しいケースに限定されそうです。
補間文字列とStringBuilder
(初期バッファ指定なし)でパラメータ数によってMeanの順位が入れ替わっているのは、StringBuilder
でDefaultCapacity
を越えたときのバッファ再確保が影響していると思われます。補間文字列の方がメモリ割り当てがかなり少ないため、ガベージコレクションまで考慮するととりあえず補間文字列を使っておけば良いと思われます。
今回の検証結果から、社内のコーディングルールに以下を追加します。
string.Format
を使わず補間文字列を使う- 補間文字列だけでロジックを構築できないときは
StringBuilder
を使う - 性能要件が厳しいときのみ
string.Create
の使用を検討する