7

これは、一言で言えばC#からのものです

StringBuilder sb = new StringBuilder();
for(int i = 0; i < 50; i++) 
     sb.Append (i + ",");

//Outputs 0,1,2,3.............49,

ただし、「式 i + "," は、文字列を繰り返し連結していることを意味しますが、文字列が小さいため、これはわずかなパフォーマンス コストしか発生しません」

それから、それを以下の行に変更すると速くなると言われています

for(int i = 0; i < 50; i++) {
    sb.Append(i.ToString()); 
    sb.Append(",");
}

しかし、なぜそれは速いのですか?iが文字列に変換される追加のステップがありますか? この内部で実際に行われていることは何ですか?この章の残りの部分では、これ以上の説明はありません。

4

3 に答える 3

15

あなたの質問に対する最初の 2 つの回答は、まったく正しくありません。sb.Append(i + ",");ステートメントは を呼び出しません。i.ToString()実際に行うことは

StringBuilder.Append(string.Concat((object)i, (object)","));

string.Concat関数の内部では、渡されToString()た 2 つobjectの が呼び出されます。このステートメントで重要なパフォーマンスの問題は です(object)i。これはボクシングです - 値の型を参照内にラップします。これは、何かをボックス化するために余分なサイクルとメモリ割り当てが必要であり、余分なガベージ コレクションが必要になるため、(比較的) かなりのパフォーマンス ヒットです。

これは、(リリース) コンパイル済みコードの IL で確認できます。

IL_000c:  box        [mscorlib]System.Int32
IL_0011:  ldstr      ","
IL_0016:  call       string [mscorlib]System.String::Concat(object,
                                                            object)
IL_001b:  callvirt   instance class [mscorlib]System.Text.StringBuilder 
                     [mscorlib]System.Text.StringBuilder::Append(string)

最初の行がboxcall であり、その後に call が続き、Concatfinally calling で終わることを確認してAppendください。

以下に示すように、代わりにコールi.ToString()すると、ボクシングとstring.Concat()コールを放棄します。

for (int i = 0; i < 50; i++)
{
    sb.Append(i.ToString());
    sb.Append(",");
}

この呼び出しにより、次の IL が生成されます。

IL_000b:  ldloca.s   i
IL_000d:  call       instance string [mscorlib]System.Int32::ToString()
IL_0012:  callvirt   instance class [mscorlib]System.Text.StringBuilder
                     [mscorlib]System.Text.StringBuilder::Append(string)
IL_0017:  pop
IL_0018:  ldloc.0
IL_0019:  ldstr      ","
IL_001e:  callvirt   instance class [mscorlib]System.Text.StringBuilder
                     [mscorlib]System.Text.StringBuilder::Append(string)

ボクシングも もありませんString.Concat。したがって、収集する必要がある生成されるリソースが少なくなり、ボクシングで浪費されるサイクルが少なくなりますが、1 つのAppend()呼び出しを追加するコストがかかります。これは比較的安価です。

これが、2 番目のコード セットの方がパフォーマンスが優れている理由です。

この考えを他の多くのものに拡張できます-値の型を引数として明示的にとらない関数に渡す文字列を操作している場所ならどこでも(たとえばobject、を引数として取る呼び出し) 、値型の引数を渡すときstring.Format()に呼び出すことをお勧めします。<valuetype>.ToString()

コメントでのテオドロスの質問に応えて:

コンパイラ チームは確かにそのような最適化を行うことを決定できましたが、コスト (追加の複雑さ、時間、追加のテストなどの観点から) により、そのような変更の価値が投資に値しないと判断したと推測されます。

基本的に、表面上はstrings で動作するが、その中でオーバーロードを提供するobject(基本的にはif (boxing occurs && overload has string)) 関数の特別なケースの分岐を配置する必要がありました。そのブランチ内で、コンパイラは、引数の呼び出しを除いて、object関数のオーバーロードがオーバーロードと同じことを行うことを確認するためにもチェックする必要があります。これを行う必要があるのは、ユーザーが 1 つの関数がもう 1 つは を受け取りますが、2 つのオーバーロードは引数に対して異なる処理を実行します。stringToString()stringobject

これは、いくつかの文字列操作関数にマイナーな最適化を行うための多くの複雑さと分析のように思えます。さらに、これは、人々が常に誤解している非常に正確なルールがすでにいくつかあるコアコンパイラ関数解決コードをいじることになります(多くの Eric Lippert の回答を見てください-かなりの数が関数解決の問題を中心に展開しています)。「その状況を除いて、このように機能する」タイプのルールでより複雑にすることは、リターンが最小限である場合は確かに避けるべきものです。

より安価で複雑でない解決策は、基本関数の解決規則を使用し、値の型 ( などint) を関数に渡すことをコンパイラに解決させ、それに適合する唯一の関数シグネチャが 1 つであることをコンパイラに判断させることです。それがかかりobject、ボックスを行います。次に、ユーザーが自分のコードをプロファイリングし、それが必要であると判断したときに最適化を行うように頼りますToString()(または、この動作について知っていて、状況に遭遇したときに常にそれを実行します)。

彼らが行うことができた可能性の高い代替手段は、s、sなど(のような)を取り、ボックス化されない内部で引数を呼び出すだけの多数のstring.Concatオーバーロードを持つことです。これには、最適化がコンパイラではなくクラス ライブラリにあるという利点がありますが、元の質問のように、連結で型を混在させたい状況に必然的に遭遇します。順列は爆発するだろう。それが彼らがそうしなかった理由である可能性が高い. 彼らはまた、そのようなオーバーロードが使用される最も一般的に使用される状況を決定し、上位 5 を実行することもできたでしょうが、私は彼らが、人々が「まあ、そうしましたが、なぜそうしないのか?」。intdoublestring.Concat(int, int)ToStringstring.Concat(int, string)(int, string)(string, int)

于 2013-08-18T01:29:42.630 に答える
4

これで、i が文字列に変換される追加のステップがありますか?

余分な手順ではありません。最初のスニペットでも、明らかに整数をどこかiで文字列に変換する必要があります。これは加算演算子によって処理されるため、表示されていない場所でも発生しますが、それでも発生します。

i.ToString()2 番目のスニペットの方が高速な理由は、 andの結果を連結して新しい文字列を作成する必要がないため","です。

最初のバージョンの機能は次のとおりです。

sb.Append ( i+",");
  1. コールしi.ToStringます。
  2. 新しいものを作成しますstring(と考えてnew string(iAsString + ",")ください)。
  3. sb.Append を呼び出します。

2 番目のバージョンの機能は次のとおりです。

  1. コールしi.ToStringます。
  2. コールしsb.Appendます。
  3. コールしsb.Appendます。

ご覧のとおり、唯一の違いは 2 番目のステップですsb.Append。2 番目のバージョンの呼び出しは、2 つの文字列を連結して結果から別のインスタンスを作成するよりも高速であると予想されます。

于 2013-08-17T22:39:30.333 に答える
0

次の場合:

string x = "abc";
x = x + "d";     // or even x += "d";

実際の 2 行目は、"abc" で値付けされた最初の文字列を破棄し、x="abcd" の新しい文字列を作成します。それがあなたが見ているパフォーマンスヒットだと思います。

于 2013-08-17T22:48:23.053 に答える