StringBuilderと同じくらい効率的なString.Formatです。


160

これを行うC#の文字列ビルダーがあるとします。

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

それは次のものと同じくらい効率的ですか、それとももっと効率的ですか?

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

もしそうなら、なぜですか?

編集

いくつか興味深い答えを見つけた後、私はおそらく私が求めていることをもう少し明確にすべきであることに気づきました。文字列を連結する方が速いのではなく、ある文字列を別の文字列に挿入する方が速いのではないかと私はそれほど質問していませんでした。

上記のどちらの場合でも、事前定義されたテンプレート文字列の中央に1つ以上の文字列を挿入します。

混乱させて申し訳ありません


今後の改善のために、これらは開いたままにしてください。
Mark Biek

4
特殊なケースのシナリオでは、これらのどちらも最速ではありません。交換するパーツのサイズが新しいパーツと等しい場合は、文字列をインプレースで変更できます。残念ながら、これにはリフレクションまたは安全でないコードが必要であり、意図的に文字列の不変性に違反します。良い方法ではありませんが、速度が問題になる場合は... :)
Abel

上記の例string s = "The "+cat+" in the hat";では、ループで使用しない限り最速になる可能性がStringBuilder あります。その場合、ループの外側で初期化すると最速になります。
Surya Pratap 2016

回答:


146

注:この回答は、.NET 2.0が現在のバージョンであるときに作成されました。これは、それ以降のバージョンには適用されない可能性があります。

String.FormatStringBuilder内部的に使用します:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

上記のコードはmscorlibのスニペットなので、質問は「StringBuilder.Append()より速いStringBuilder.AppendFormat()」ですか?

ベンチマークがなければ、上記のコードサンプルはを使用するとより高速に実行されるとおそらく言え.Append()ます。しかし、それは推測です。適切に比較するために、2つをベンチマークまたはプロファイリングしてみてください。

この章、Jerry Dixonはいくつかのベンチマークを行いました:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

更新しました:

悲しいことに、上記のリンクはその後亡くなりました。ただし、Way Back Machineにはまだコピーがあります。

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

結局のところ、文字列フォーマットが繰り返し呼び出されるかどうか、つまり、数百メガバイトのテキストを超える深刻なテキスト処理を行っているかどうか、またはユーザーがボタンを何度もクリックしたときに呼び出されるかどうかによって異なります。巨大なバッチ処理ジョブを実行しているのでない限り、String.Formatを使い続けると、コードが読みやすくなります。パフォーマンスのボトルネックが疑われる場合は、プロファイラーをコードに貼り付け、実際の場所を確認します。


8
Jerry Dixonのページのベンチマークに関する1つの問題は、彼.ToString()StringBuilderオブジェクトを呼び出さないことです。非常に多くの反復にわたって、その時間は大きな違いを生み、彼はリンゴとリンゴを完全に比較しているわけではありません。それが彼がそのような素晴らしいパフォーマンスを見せStringBuilder、おそらく彼の驚きを説明する理由です。私はちょうどその間違いを訂正するベンチマークを繰り返し、期待される結果を得た:String +オペレータは最速だった、が続くStringBuilder、とString.Formatリアを育てます。
ベンコリンズ

5
6年後、これはもはやそうではありません。Net4では、string.Format()は再利用するStringBuilderインスタンスを作成してキャッシュするため、一部のテストケースではStringBuilderよりも高速になる場合があります。以下の回答に改訂されたベンチマークを入れました(それでもconcatが最速であり、私のテストケースでは、形式はStringBuilderよりも10%遅くなっています)。
クリスFキャロル

45

MSDNドキュメントから:

StringまたはStringBuilderオブジェクトの連結操作のパフォーマンスは、メモリ割り当てが発生する頻度に依存します。String連結操作は常にメモリを割り当てますが、StringBuilder連結操作は、StringBuilderオブジェクトバッファが小さすぎて新しいデータを収容できない場合にのみメモリを割り当てます。したがって、固定数のStringオブジェクトが連結される場合、連結操作にはStringクラスが適しています。その場合、個々の連結操作は、コンパイラーによって単一の操作に結合されることさえあります。StringBuilderオブジェクトは、任意の数の文字列を連結する場合の連結操作に適しています。たとえば、ループがランダムな数のユーザー入力の文字列を連結する場合などです。


12

私はいくつかのクイックパフォーマンスベンチマークを実行しました。平均10回の実行で100,000回の操作の場合、最初の方法(文字列ビルダー)は2番目の方法(文字列形式)のほぼ半分の時間です。

したがって、これがまれである場合は問題ではありません。ただし、それが一般的な操作である場合は、最初の方法を使用できます。


10

私は期待String.Formatのは、それは文字列を解析しており、 -遅いとその後、それを連結します。

いくつかのメモ:

  • フォーマットは、プロフェッショナルアプリケーションでユーザーに表示される文字列を取得する方法です。これはローカリゼーションのバグを回避します
  • 結果の文字列の長さが事前にわかっている場合は、StringBuilder(Int32)コンストラクタを使用して容量を事前定義します

8

私はほとんどの場合、効率ではなく、この明快さのようにあなたの最大の関心事になるはずだと思います。大量のストリングをつぶしたり、低出力のモバイルデバイス用に何かを構築したりしているのでない限り、これはおそらく実行速度に大きな影響を与えることはありません。

文字列を直線的に構築している場合は、直接連結するか、StringBuilderを使用するのが最適なオプションであることがわかりました。これは、作成している文字列の大部分が動的である場合にお勧めします。静的なテキストはほとんどないため、最も重要なことは、将来更新する必要がある場合に備えて、動的テキストの各部分がどこに配置されるかが明確になっていることです。

一方、2つまたは3つの変数が含まれている静的テキストの大きなチャンクについて話している場合、たとえそれが少し効率的でなくても、string.Formatから得られる明快さは価値があると思います。今週の初めにこれを使用して、4ページのドキュメントの中央に1ビットのダイナミックテキストを配置する必要がありました。連結した3つの部分を更新する必要があるよりも、1つの部分でその大きなテキストのチャンクを更新する方が簡単です。


はい!文字列をフォーマットする場合など、意味がある場合はString.Formatを使用します。機械的連結を実行する場合は、文字列連結またはStringBuilderを使用します。次のメンテナにあなたの意図を伝える方法を選ぶように常に努めてください。
ロブ

8

string.Formatが正確にあなたの考えていることを実行しないためだけに、6年後のNet45でのテストの再実行を以下に示します。

Concatは依然として最速ですが、実際には30%未満の差です。StringBuilderとFormatはわずか5〜10%異なります。テストを数回実行して20%のバリエーションを得ました。

ミリ秒、100万回の反復:

  • 連結:367
  • 各キーの新しいstringBuilder:452
  • キャッシュされたStringBuilder:419
  • string.Format:475

私が取り上げる教訓は、パフォーマンスの違いは些細なものであるため、できる限り単純で読みやすいコードを書くことを止めるべきではないということです。私のお金はどれだけですが、いつもというわけではありませんa + b + c

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);

2
「string.Formatはあなたの考えているとおりに動作しない」とは、4.5のソースコードでは、キャッシュされたStringBuilderインスタンスを作成して再利用しようとすることを意味します。テストにそのアプローチを含めました
Chris F Carroll

6

String.FormatはStringBuilder内部的に使用します。論理的には、オーバーヘッドが大きくなるため、パフォーマンスが少し低下するという考えにつながります。ただし、単純な文字列連結は、2つの文字列の間に1つの文字列を注入する最も高速な方法です。この証拠は、数年前の彼の最初のパフォーマンスクイズでRico Marianiによって示されました。単純な事実は、連結は...文字列の部分の数がわかっている場合(制限なしに... 1,000の部分を連結することができます...常に1000の部分を知っている限り)...は、StringBuilderまたはString よりも常に速いということです。フォーマット。これらは、単一のメモリ割り当てと一連のメモリコピーで実行できます。ここに証明があります

そして、いくつかのString.Concatメソッドの実際のコードは次のとおりです。最終的には、ポインタを使用してメモリをコピーするFillStringCheckedを呼び出します(Reflectorで抽出)。

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

それで:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

楽しい!


Net4では、string.FormatはStringBuilderインスタンスをキャッシュして再利用するため、一部の使用ではより高速になる場合があります。
クリスFキャロル

3

また、最速は次のようになります。

string cat = "cat";
string s = "The " + cat + " in the hat";

いいえ、.NETは連結操作の間に文字列変数の追加のコピーを作成するため(この場合、2つの追加のコピーと割り当ての最終コピー)、文字列の連結は非常に遅くなります。結果:StringBuilderもともとこのタイプのコーディングを最適化するために作成されたものと比較して、パフォーマンスは非常に低くなります。
Abel、

たぶんタイプするのが
一番速い

2
@Abel:答えは詳細に欠けているかもしれませんが、このアプローチは、この特定の例では、最も速いオプションです。コンパイラーはこれを単一のString.Concat()呼び出しに変換するため、StringBuilderで置き換えると実際にコードの速度が低下します。
Dan C.

1
@Vaibhavは正しいです。この場合、連結が最も高速です。もちろん、その違いは、何度も繰り返されない限り、またはおそらく、はるかに大きなストリングで操作されない限り、重要ではありません。
ベンコリンズ

0

それは本当に依存します。連結が少ない小さな文字列の場合、実際には文字列を追加するだけの方が高速です。

String s = "String A" + "String B";

ただし、より大きな文字列(非常に大きな文字列)の場合は、StringBuilderを使用する方が効率的です。


0

上記のどちらの場合でも、事前定義されたテンプレート文字列の中央に1つ以上の文字列を挿入します。

その場合、String.Formatは、まさにその目的のための設計であるため、最速をお勧めします。



-1

String.Formatは連結用に設計されていないため、日付などのさまざまな入力の出力をフォーマットするための設計でした。

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.