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