C# の文字列連結方法についてベンチマークしてみた

2 min読了の目安(約2400字TECH技術記事

はじめに

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() するとパフォーマンスが最大化されます。