web-dev-qa-db-ja.com

StringBuilder.Append Vs StringBuilder.AppendFormat

StringBuilderについて疑問に思っていましたが、コミュニティが説明できることを望んでいたという質問があります。

コードの読みやすさを忘れてみましょう。どれがfasterで、なぜですか?

StringBuilder.Append

StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);

StringBuilder.AppendFormat

StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}",string1,string2);
36
Sergio

string1string2のサイズがわからないため、言うことは不可能です。

AppendFormat を呼び出すと、フォーマット文字列と挿入される文字列の長さが指定されると、バッファが事前に割り当てられ、すべてが連結されてバッファに挿入されます。非常に大きな文字列の場合、これは Append を個別に呼び出すよりも有利であり、バッファが複数回拡張される可能性があります。

ただし、Appendへの3つの呼び出しは、バッファーの増大をトリガーする場合とトリガーしない場合があり、その検査は呼び出しごとに実行されます。文字列が十分に小さく、バッファー拡張がトリガーされない場合、AppendFormatの呼び出しよりも高速になります。これは、置換を行う場所を見つけるためにフォーマット文字列を解析する必要がないためです。

最終的な回答にはさらにデータが必要です

静的な ConcatメソッドをStringクラスで使用することについてはほとんど議論されていないJon's answer using AppendWithCapacityはこれを思い出しました)。彼のテスト結果は、それが最良のケースであることを示しています(特定のフォーマット指定子を利用する必要がないと仮定した場合)。 String.Concatは、バッファを連結および事前割り当てする文字列の長さを事前に決定する(パラメータを介したループ構造のため、オーバーヘッドがわずかに増える)点で同じことを行います。パフォーマンスはジョンのAppendWithCapacityメソッドに匹敵します。

または、単純な加算演算子だけです。コンパイルはString.Concatへの呼び出しにコンパイルされるため、すべての加算が同じ式に含まれることに注意してください。

// One call to String.Concat.
string result = a + b + c;

[〜#〜]ではない[〜#〜]

// Two calls to String.Concat.
string result = a + b;
result = result + c;

テストコードを作成するすべての人のために

テストケースをseparate実行で実行する必要があります(少なくとも、個別のテスト実行の測定の間にGCを実行します)。これは、1,000,000回実行すると、1つのテストのループの反復ごとに新しい StringBuilder が作成され、次にループする次のテストが実行されるためです。同じ回数、追加1,000,000 StringBuilderインスタンスを作成すると、GCは2番目のテスト中に介入し、そのタイミングを妨げます。

42
casperOne

casperOneは正しい 。特定のしきい値に達すると、Append()メソッドはAppendFormat()よりも遅くなります。次に、各メソッドの100,000回の反復の異なる長さと経過ティックを示します。

長さ:1

_Append()       - 50900
AppendFormat() - 126826
_

長さ:1000

_Append()       - 1241938
AppendFormat() - 1337396
_

長さ:10,000

_Append()       - 12482051
AppendFormat() - 12740862
_

長さ:20,000

_Append()       - 61029875
AppendFormat() - 60483914
_

長さが20,000に近い文字列が導入されると、AppendFormat()関数は少しAppend()よりもパフォーマンスが向上します。

なぜこれが起こるのですか? casperOne's answer を参照してください。

編集:

リリース構成で各テストを個別に再実行し、結果を更新しました。

22
John Rasch

casperOneはデータに依存するため、完全に正確です 。ただし、これをサードパーティが使用するクラスライブラリとして作成しているとします。どちらを使用しますか?

1つのオプションは、両方の長所を利用することです。実際に追加する必要のあるデータの量を計算し、次に StringBuilder.EnsureCapacity を使用して、単一のバッファーのサイズ変更のみが必要であることを確認します。 。

私がtoo気にならなかった場合は、Append x3を使用します-すべての呼び出しで文字列形式のトークンを解析することで、 -作業。

BCLチームに、フォーマット文字列を使用して作成し、繰り返し再利用できる「キャッシュフォーマッター」を要求したことに注意してください。フレームワークが使用されるたびにフォーマット文字列を解析しなければならないのはおかしいです。

編集:さて、私はジョンのコードをいくらか編集して柔軟性を高め、必要な容量を最初に計算する「AppendWithCapacity」を追加しました。これがさまざまな長さの結果です-長さ1の場合、1,000,000回の反復を使用しました。他のすべての長さでは、100,000を使用しました。 (これは、適切な実行時間を取得するためだけでした。)すべての時間はミリ秒単位です。

残念ながら、テーブルは実際にはSOでは機能しません。長さは1、1000、10000、20000でした

時間:

  • 追加:162、475、7997、17970
  • AppendFormat:392、499、8541、18993
  • AppendWithCapacity:139、189、1558、3085

そのため、AppendFormatがAppendに勝つことはありませんでしたが、私はdid AppendWithCapacityがかなりの差で勝つことを確認しました。

完全なコードは次のとおりです。

using System;
using System.Diagnostics;
using System.Text;

public class StringBuilderTest
{            
    static void Append(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendWithCapacity(string string1, string string2)
    {
        int capacity = string1.Length + string2.Length + 4;
        StringBuilder sb = new StringBuilder(capacity);
        sb.Append(string1);
        sb.Append("----");
        sb.Append(string2);
    }

    static void AppendFormat(string string1, string string2)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}----{1}", string1, string2);
    }

    static void Main(string[] args)
    {
        int size = int.Parse(args[0]);
        int iterations = int.Parse(args[1]);
        string method = args[2];

        Action<string,string> action;
        switch (method)
        {
            case "Append": action = Append; break;
            case "AppendWithCapacity": action = AppendWithCapacity; break;
            case "AppendFormat": action = AppendFormat; break;
            default: throw new ArgumentException();
        }

        string string1 = new string('x', size);
        string string2 = new string('y', size);

        // Make sure it's JITted
        action(string1, string2);
        GC.Collect();

        Stopwatch sw = Stopwatch.StartNew();
        for (int i=0; i < iterations; i++)
        {
            action(string1, string2);
        }
        sw.Stop();
        Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds);
    }
}
12
Jon Skeet

Append コンパイラーが正しいメソッドを呼び出せるようにする、そのメソッドへの多くのオーバーロードがあるため、ほとんどの場合、より高速になります。 Stringsを使用しているため、StringBuilderStringAppendオーバーロードを使用できます。

AppendFormatStringを受け取り、次にObject[]を受け取ります。これは、フォーマットを解析する必要があることと、配列内の各ObjectToString'd内部配列に追加する前に、StringBuilder'sである必要があります。

注:casperOneの要点-より多くのデータなしに正確な答えを出すことは困難です。

6
Andrew Hare

StringBuilderにもカスケードされたアペンドがあります:Append()StringBuilder自体を返すため、次のようにコードを記述できます。

StringBuilder sb = new StringBuilder();
sb.Append(string1)
  .Append("----")
  .Append(string2);

クリーンで、ILコードが少なくなります(ただし、これは実際にはマイクロ最適化です)。

1
Tommy Carlier

もちろん、それぞれの場合に確実にわかるプロファイル。

とは言っても、フォーマット文字列を繰り返し解析しないので、一般的には前者になると思います。

ただし、その差はごくわずかです。とにかく、ほとんどの場合、AppendFormatの使用を本当に検討する必要がある点まで。

1
Joel Coehoorn

作業量が最も少ないのは電話だったと思います。 AppendFormatが文字列の置換を行う場合、Appendは単に文字列を連結します。もちろん、最近では決してわかりません...

0
Paul W Homer

1は文字列を追加するだけなので、より高速になるはずですが、2は形式に基づいて文字列を作成してから、文字列を追加する必要があります。だからそこに余分なステップがあります。

0
Micah

あなたの場合、Fasterは1ですが、公平な比較ではありません。 StringBuilder.AppendFormat()StringBuilder.Append(string.Format())を確認する必要があります。char配列を内部で処理するため、最初の方が高速です。

2番目のオプションはより読みやすいです。

0
Miha Markic