C# の文字列連結方法についてベンチマークしてみた
はじめに
C# に於ける「文字列の繋げ方」には幾つかの方法があります。
- 文字列連結演算子
+(String.Concat()メソッドの糖衣構文) -
String.Format()メソッド -
StringBuilder.Append()メソッド -
StringBuilder.AppendFormat()メソッド - string interpolation (文字列補間)
んで、よく言われるのは「 + での連結はパフォーマンス的に不利だから、 StringBuilder.Append() を使え」というヤツです。
実際、 + での連結は上述の通り、コンパイラによって String.Concat() に変換されるので、2つの String インスタンスから新しい String インスタンスを生成するというコトになり、処理速度的にもメモリ的にもかなり不利だったりするわけです。
で、今回
💭。oO(
+が遅いのはまぁそうなんだろうけど、例えばStringBuilder.Append()に渡す文字列が string interpolation なのと、StringBuild.Append().Append()...とだとどっちが速いんだろう? 🤔)
とか思ってしまい、ベンチマークを採ってみたので、その結果を記事にしてみました。
TL; DR
StringBuilder.Append() の圧勝!!!
詳細
ベンチマーク方法
BenchmarkDotNet を使ってシンプルなベンチマークを採ってみました。
シナリオとしては
- 1,000回のループの中で、ループ回数を文字列として連結
- 連結時に Prefix, Suffix を付ける
といった感じ。
実際のコードは GitHub で公開しています。
結果
| Method | Mean | Error | StdDev | Allocated |
|---|---|---|---|---|
String.Format() |
971.36 μs | 12.731 μs | 11.285 μs | 14324.43 KB |
| 文字列補間 | 405.63 μs | 5.367 μs | 4.758 μs | 4761.95 KB |
String.Concat() |
365.94 μs | 6.989 μs | 6.196 μs | 4871.04 KB |
| 文字列連結演算子 | 313.47 μs | 3.249 μs | 2.880 μs | 4738.23 KB |
StringBuilder.Append() (文字列補間) |
131.18 μs | 2.069 μs | 1.935 μs | 80.97 KB |
StringBuilder.AppendFormat() |
95.23 μs | 1.021 μs | 0.955 μs | 49.94 KB |
StringBuilder.Append() |
33.49 μs | 0.383 μs | 0.340 μs | 26.5 KB |
※JetBrains Rider 的に sudo を付けて実行できず、ベンチマークプロセスの優先度変更ができていないため、数値はそこまで正確じゃないかも。 |
何と、 StringBuffer.Append() は String.Format() の約30倍速い!ヒープアロケーションに至っては約1/550!!!
正直、ここまで差が出るとは思っていませんでした。
ベンチマークのコードが悪いのかと思って、何度か見直したレレル。
考察
-
String.Format()は内部で色々複雑なコトやってそうだし、Alloc が大きいのもまぁ分かる。 - 文字列補間はコンパイラが最適化してくれるらしいので、今回は
String.Concat()が利用されたのかな? -
String.Concat()と文字列連結演算子との差は誤差だと思われ。 -
StringBuilder.Append()に文字列補間で作った文字列を渡すよりはStringBuilder.AppendFormat()で渡した方が良いってのは、結局内部で文字列補間してる文字列がString.Concat()とかに変換されちゃうから、かな?
まとめ
高パフォーマンスを必要とする箇所で文字列を連結する時は、 StringBuilder.Append() を使いましょう。
引数に渡す文字列も横着せずに、細かく .Append() するとパフォーマンスが最大化されます。
Discussion