2013-07-10 10 views
13

String.Contains方法は、ここで別のコールは、関連するCompareInfo.IndexOfメソッドを呼び出して、最終的な過負荷に行われるこのString.Containsが最後のオーバーロードを直接呼び出すのはなぜですか?

public int IndexOf(string value, StringComparison comparisonType) 
{ 
    return this.IndexOf(value, 0, this.Length, comparisonType); 
} 

のようなルックスと呼ばれ、この内部

public bool Contains(string value) 
{ 
    return this.IndexOf(value, StringComparison.Ordinal) >= 0; 
} 

IndexOf過負荷のように見えます署名付き

public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType) 

したがって、最終的な過負荷は最も速くなります(ほとんどの場合、マイクロ最適化と見なすこともできます)。 メソッドは、中間コールで他の作業が行われていないことと、同じ情報が両方のステージで利用可能であることを考慮して、最終的なオーバーロードを直接呼び出さないのはなぜですか?

最終的なオーバーロードのシグネチャが変更された場合、変更が1回だけ(中間方法のもの)必要であるか、それ以上にデザインが増えるという唯一の利点はありますか?私はどこかでミスを犯してきた場合には取得していますパフォーマンスの違いを明確にコメントから

編集(速度差の説明のための2を更新参照)

: は、私は(this benchmarkを走ったが5回ループジッタバイアスを回避するため)、ループはこの

0123のように見えると String.Contains方法

public static bool QuickContains(this string input, string value) 
{ 
    return input.IndexOf(value, 0, input.Length, StringComparison.OrdinalIgnoreCase) >= 0; 
} 

と比較するために、この拡張メソッドを使用

ベンチマークテストでは、QuickContainsは私のマシンのString.Containsより約50%速いようです。

アップデート2(パフォーマンスの違いを説明)

私は多くのことを説明し、ベンチマークで不公平なものを発見しました。大文字と小文字を区別しない文字列を測定するベンチマーク自体でしたが、String.Containsは大文字と小文字を区別した検索しか実行できないため、ToUpperメソッドが含まれていました。これは結果を歪ませますが、少なくとも最終的な出力に関してではなく、少なくとも大文字小文字を区別しない検索ではString.Containsのパフォーマンスを単純に測定する点では、

私はこの拡張メソッド

public static bool QuickContains(this string input, string value) 
{ 
    return input.IndexOf(value, 0, input.Length, StringComparison.Ordinal) >= 0; 
} 

2過負荷IndexOf呼び出しで使用StringComparison.Ordinalを使用してToUpperを削除する場合だから今、QuickContains方法は、実際には、最も遅いとなります。 IndexOfContainsは、パフォーマンス面でかなり上です。だから明らかにとIndexOfの間にこのような不一致があった理由の結果を歪曲させてToUpper呼び出しでした。

QuickContains拡張メソッドが最も遅くなった理由がわかりません。(おそらく、Containsには[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]という属性がありますか?)

4オーバーロードメソッドが直接呼び出されない理由はまだありますが、パフォーマンスによって影響を受けていないようです(Adrianとdelnanがコメントで指摘したように)。

+0

これは、メソッドの呼び出しがコンパイラによってインライン化される可能性がありますので、任意の'String.Contains(文字列値)'の出現は、コードが実行されるまでにもっと複雑なバージョンに既に書き直されている可能性があります。しかし、これは明らかに単なる推測である。 –

+0

それは良い考えです。私はちょうど最後のオーバーロードを直接呼び出す独自の 'Contains'メソッドを作成することでベンチマークを試みました。そして、それは既存の' Contains'メソッドよりも50%速くなったので、顕著なパフォーマンスヒットがあります。 1mループより200ms)。 – keyboardP

+0

@keyboardP私は少し信じがたいことがわかります。最も単純なインライン・ヒューリスティックと最も単純なインライナーであっても、2つのバリアントを見分けがたいものにする必要があります。あなたは[ベンチマーキングミス](http://tech.pro/blog/1293/c-performance-benchmark-mistakes-part-one)をコミットしていませんか? – delnan

答えて

5

MSILとJITについては、私はアセンブリに目を通していましたが、これは何もないことを知っています。これはいい運動です。抵抗できないので、ちょっとだけです冗長で経験的なデータ。 IndexOfオーバーロードがインライン化されますか?

class Program 
{ 
    static void Main(string[] args) 
    { 
     "hello".Contains("hell"); 
    } 
} 

JITは32ビットで動作して、最適化されたリリースビルド、任意のCPUでこれを生成します。

はここで小さなコンソールアプリです。私はアドレスを短縮し、そしていくつかの無関係な行削除しました:

--- ...\Program.cs 
      "hello".Contains("hell"); 
[snip] 
17 mov   ecx,dword ptr ds:[0320219Ch] ; pointer to "hello" 
1d mov   edx,dword ptr ds:[032021A0h] ; pointer to "hell" 
23 cmp   dword ptr [ecx],ecx 
25 call  680A6A6C      ; String.Contains() 
[snip] 

0x00000025でcallは、ここに行く:

00 push  0     ; startIndex = 0 
02 push  dword ptr [ecx+4] ; count = this.Length (second DWORD of String) 
05 push  4     ; comparisonType = StringComparison.Ordinal 
07 call  FF9655A4   ; String.IndexOf() 
0c test  eax,eax 
0e setge  al    ; if (... >= 0) 
11 movzx  eax,al 
14 ret 

は案の定、呼び出しているようだ

String.Contains 、直接、最後のString.IndexOf 4つの引数を持つオーバーロード:3 push ed; edxvalue: "hell")のいずれかです。 ecxthis( "hello")。 0x00000005でcallがどこに行くを確認するには、これは次のとおりです。身体のだろう

00 push  ebp 
01 mov   ebp,esp 
03 push  edi 
04 push  esi 
05 push  ebx 
06 mov   esi,ecx     ; this ("hello") 
08 mov   edi,edx     ; value ("hell") 
0a mov   ebx,dword ptr [ebp+10h] 
0d test  edi,edi     ; if (value == null) 
0f je   00A374D0 
15 test  ebx,ebx     ; if (startIndex < 0) 
17 jl   00A374FB 
1d cmp   dword ptr [esi+4],ebx ; if (startIndex > this.Length) 
20 jl   00A374FB 
26 cmp   dword ptr [ebp+0Ch],0 ; if (count < 0) 
2a jl   00A3753F 
[snip] 

...:

public int IndexOf(string value, 
        int startIndex, 
        int count, 
        StringComparison comparisonType) 
{ 
    if (value == null) 
    throw new ArgumentNullException("value"); 
    if (startIndex < 0 || startIndex > this.Length) 
    throw new ArgumentOutOfRangeException("startIndex", 
      Environment.GetResourceString("ArgumentOutOfRange_Index")); 
    if (count < 0 || startIndex > this.Length - count) 
    throw new ArgumentOutOfRangeException("count", 
      Environment.GetResourceString("ArgumentOutOfRange_Count")); 
    ... 
} 
+0

うわー、努力のおかげで!パフォーマンスの差は(大文字と小文字を区別しない検索では)無視できるので、設計上の決定になります。私は、4オーバーロードシグネチャが変わっても、2つのコード変更を避ける必要があるかもしれないと思います。 – keyboardP

+0

もちろん、今私はコールスタックが私にこれを教えてくれることに気付きました。それにもかかわらず、JITのアウトプットへの私の最初の本当の見方を持つことは楽しいです。 – JimmiTh

+0

私は実際の学位を巡って議会を掘り下げなかったので、あなたの答えは非常に面白いです。 – keyboardP

関連する問題