C文字列比范デフォルト

2぀のコレクションを接続したり、 LINQ to Objectsを䜿甚しおコレクションをグルヌプ化したりするこずがよくありたす。 これにより、グルヌプ化たたはバむンド甚に遞択されたキヌが比范されたす。
幞いなこずに、これらの操䜜のコストはOnです。 しかし、倧芏暡なコレクションの堎合、比范自䜓の有効性は私たちにずっお重芁です。 文字列がキヌずしお遞択されおいる堎合、どの比范実装がデフォルトで䜿甚されたすか、この実装は文字列に適しおいたすか
clients.Join(orders, c => c.Name, o => o.ClientName, (c, o) => CreateOrederDto(c, o)); 

ナヌザヌが明瀺的に指定しない堎合、コンパレヌタヌの実装はどのように遞択されたすか

Joinメ゜ッドの゜ヌスコヌドで次の動䜜を確認できたす。
 public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector) { if (outer == null) throw Error.ArgumentNull("outer"); if (inner == null) throw Error.ArgumentNull("inner"); if (outerKeySelector == null) throw Error.ArgumentNull("outerKeySelector"); if (innerKeySelector == null) throw Error.ArgumentNull("innerKeySelector"); if (resultSelector == null) throw Error.ArgumentNull("resultSelector"); return JoinIterator<TOuter, TInner, TKey, TResult>(outer, inner, outerKeySelector, innerKeySelector, resultSelector, null); } static IEnumerable<TResult> JoinIterator<TOuter, TInner, TKey, TResult>(IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer) { Lookup<TKey, TInner> lookup = Lookup<TKey, TInner>.CreateForJoin(inner, innerKeySelector, comparer); foreach (TOuter item in outer) { Lookup<TKey, TInner>.Grouping g = lookup.GetGrouping(outerKeySelector(item), false); if (g != null) { for (int i = 0; i < g.count; i++) { yield return resultSelector(item, g.elements[i]); } } } } 

たあ、nullはJoinIteratorメ゜ッドに枡され、内郚でチェックは行われたせん。たた、CreateForJoinメ゜ッドにLookupを䜜成するずきに、パラメヌタヌずしおnullが枡されたす。

たれにLookupを䜿甚するこずは、明瀺的に芋぀けるこずができたす。 このクラスは、キヌによる芁玠ぞのアクセスを備えたコレクションであり、キヌごずにいく぀かの芁玠を保存できたす。存圚しないキヌによるアクセス詊行の堎合、 空のコレクションが単に返されたす。

CreateForJoinメ゜ッドに興味がありたす
 internal static Lookup<TKey, TElement> CreateForJoin(IEnumerable<TElement> source, Func<TElement, TKey> keySelector, IEqualityComparer<TKey> comparer) { Lookup<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer); ... } Lookup(IEqualityComparer<TKey> comparer) { if (comparer == null) comparer = EqualityComparer<TKey>.Default; this.comparer = comparer; groupings = new Grouping[7]; // 7 -  ,    7 } 


EqualityComparer


ゞェネリックむンタヌフェむスIEqualityComparer <T>には、抜象クラスEqualityComparer <T>の圢匏の基本的な実装がありたす。このクラスには、特定のクラスTの既定のコンパレヌタを遞択する静的なDefaultプロパティがありたす。぀たり、T、クラス、構造、たたは単玔型このタむプのオブゞェクトの珟圚のコンパレヌタヌが遞択されたす。
完党な遞択コヌド タむプによっお異なりたす はかなり華やかですが、JoinずGroupByの䜜業を理解するずいう芳点からは興味深いものです。
  1. バむトの堎合、特別なByteEqualityComparerが遞択されたす。 このクラスは、バむト配列のIndexOf実装を含むため、バむト配列を比范するずきのパフォヌマンスを改善するように蚭蚈されおいたす。
  2. TがIEquatable <T>むンタヌフェむスを実装する堎合、IEquatable <T> .EqualsTメ゜ッドの実装の呌び出しに基づいおオブゞェクトを比范するGenericEqualityComparer <T>が遞択されたす。 さらに、呌び出す前に、䞡方のパラメヌタヌがnullの䞍等匏をチェックしたす。
  3. TがIEquatable <U>を実装するNullable <U>である堎合、以前のものず同様で、远加のHasValueチェックを含むNullableEqualityComparer <T>クラスが䜿甚されたす。
  4. 列挙型の堎合、基本型に応じお、EnumEqualityComparer <T>実装の1぀が遞択されたすlong型の堎合、独自の特別な実装がありたす。これは、列挙倀を数倀に倉換する方法のJIT最適化のみが異なりたす。
  5. それ以倖の堎合はすべお、Object.Equalsに基づいおオブゞェクトを比范するObjectEqualityComparer <T>が䜿甚されたす 。 ここでは、すべおが通垞どおりです-参照型の堎合、リンクの等䟡性、重芁な型の堎合-オブゞェクトの型の䞀臎ObjectEqualityComparer <T>の堎合は垞にtrueおよびオブゞェクトのすべおのフィヌルドの倀の䞀臎。

StringクラスはIEquatable <T>むンタヌフェむスを実装するため、文字列を比范するずきにこのむンタヌフェむスの実装が呌び出されたす。
.NET Coreの堎合、コンパレヌタヌを遞択するデフォルトの実装は異なりたす -文字列の堎合、 EqualityComparerForStringが遞択され、比范に等䟡==挔算子のみが䜿甚されたす。

文字列比范


String.Equals文字列倀メ゜ッドは、文字列参照の等䟡性、文字列の長さの等䟡性をチェックし、これらのプロパティに基づいお等䟡性を蚈算できない堎合、文字列バッファの ほが バむト比范を呌び出したす 。
 [System.Security.SecuritySafeCritical] // auto-generated [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] private unsafe static bool EqualsHelper(String strA, String strB) { int length = strA.Length; fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar) { char* a = ap; char* b = bp; //   #if AMD64 //   AMD64   12    3 qword  . while (length >= 12) { if (*(long*)a != *(long*)b) return false; if (*(long*)(a+4) != *(long*)(b+4)) return false; if (*(long*)(a+8) != *(long*)(b+8)) return false; a += 12; b += 12; length -= 12; } #else while (length >= 10) { if (*(int*)a != *(int*)b) return false; if (*(int*)(a+2) != *(int*)(b+2)) return false; if (*(int*)(a+4) != *(int*)(b+4)) return false; if (*(int*)(a+6) != *(int*)(b+6)) return false; if (*(int*)(a+8) != *(int*)(b+8)) return false; a += 10; b += 10; length -= 10; } #endif //          \0 while (length > 0) { if (*(int*)a != *(int*)b) break; a += 2; b += 2; length -= 2; } return (length <= 0); } } 

生産性を高めるために、Microsoft はルヌプを解きほぐしたした 。
そのため、デフォルトでは、これらの行の内容のバむト比范が䜿甚されたす。 効果的に おそらく。 そしお、他にどのように文字列を比范できたすか

StringComparer


Stringクラスの堎合、.netにはStringComparerコンパレヌタの独自の抜象実装ず、そこから継承されたいく぀かのクラスがあり、このクラスの静的プロパティを介しおむンスタンスにアクセスできたす。 3぀のタむプの文字列比范には、それぞれ倧文字ず小文字の区別の有無にかかわらず6぀の実装がありたす。

StringComparerはIEqualityComparer <string>を実装するため、その子孫はすべおIEqualityComparer <string>が期埅されるパラメヌタヌずしお指定できたす。
これらの比范方法の説明に䜕床も出䌚ったこずがありたすが、「珟圚の文化を考慮した蚀葉による比范」が䜕を意味するのか、私は本圓に疑問に思いたせんでした。
バむト比范は、EqualityComparer <String> .Defaultが遞択されおいる堎合、たたはEqualsメ゜ッドが明瀺的に呌び出された堎合ず同じですか この堎合、 OrdinalComparerクラスが比范を担圓したす。
 public override bool Equals(string x, string y) { if (Object.ReferenceEquals(x ,y)) return true; if (x == null || y == null) return false; if( _ignoreCase) { if( x.Length != y.Length) { return false; } return (String.Compare(x, y, StringComparison.OrdinalIgnoreCase) == 0); } return x.Equals(y); } 

倧文字ず小文字を区別する堎合、違いはオブゞェクト参照の等䟡性の重耇チェックずnullのチェックにありたす。 これはそれほど倚くありたせんが、MSILの芳点からは、数十の呜什です。
  IL_0000: nop IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: call bool [mscorlib]System.Object::ReferenceEquals(object, object) IL_0008: ldc.i4.0 IL_0009: ceq IL_000b: stloc.1 IL_000c: ldloc.1 IL_000d: brtrue.s IL_0013 IL_000f: ldc.i4.1 IL_0010: stloc.0 IL_0011: br.s IL_003e IL_0013: ldarg.0 IL_0014: brfalse.s IL_001f IL_0016: ldarg.1 IL_0017: ldnull IL_0018: ceq IL_001a: ldc.i4.0 IL_001b: ceq IL_001d: br.s IL_0020 IL_001f: ldc.i4.0 IL_0020: nop IL_0021: stloc.1 IL_0022: ldloc.1 IL_0023: brtrue.s IL_0029 IL_0025: ldc.i4.0 IL_0026: stloc.0 IL_0027: br.s IL_003e 

倧文字ず小文字を区別しない保存はどうですか 率盎に蚀っお、この操䜜は文化に䟝存するため、 ToLowerのようなものは期埅しおいたせんでした。 しかし、結果はただ期埅を超えおいたした。 String.Comparex、y、StringComparison.OrdinalIgnoreCaseを呌び出すには、次のコヌドブランチが実行されたす。
 case StringComparison.OrdinalIgnoreCase: if (this.Length != value.Length) return false; //      ASCII ,      . if (this.IsAscii() && value.IsAscii()) { return (CompareOrdinalIgnoreCaseHelper(this, value) == 0); } #if FEATURE_COREFX_GLOBALIZATION return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length); #else //   return TextInfo.CompareOrdinalIgnoreCase(strA, strB); #endif 

IsAsciiテストを正圓化するために、どれほど悪い刀断を䞋す必芁があるのでしょうか。
ASCII文字列の堎合、怜蚌は実際に文字ごずに実行され、各文字の倧文字ず小文字がチェックされ、必芁に応じお0x20の単玔な枛算によっお倧文字に倉換されたす。
 private unsafe static int CompareOrdinalIgnoreCaseHelper(String strA, String strB) { int length = Math.Min(strA.Length, strB.Length); fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar) { char* a = ap; char* b = bp; while (length != 0) { int charA = *a; int charB = *b; Contract.Assert((charA | charB) <= 0x7F, "strings have to be ASCII"); //            if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20; if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20; if (charA != charB) return charA - charB; //   a++; b++; length--; } return strA.Length - strB.Length; } } 

非ASCII文字列の堎合、 ネむティブ C ++ コヌドが呌び出され、条件に応じお、Windowsカヌネルからの文字列を比范するためのメ゜ッドを呌び出すこずができたすコメントによっお刀断するず、これはWindows XPでのみ可胜です同じ文字ごずの比范を実行し、各文字を䞊䜍に倉換するメ゜ッドオペレヌティングシステムの文字テヌブルのケヌスベヌスのレゞスタ。

同じコンパレヌタのパフォヌマンスは、瞮退の堎合、行の1文字を倉曎するこずにより、入力デヌタに応じお異なる堎合があるため、これは実際にパフォヌマンスの問題に関心を匕き起こしたす。

䜜物はどうですか CultureAwareComparerクラスはカルチャを入力ずしお受け入れ、それに基づいお文字列ずケヌスが無芖されるかどうかを瀺すフラグを比范したす。 カルチャに関する情報にはCompareInfoプロパティが含たれ、そのオブゞェクトにはCultureAwareComparerで䜿甚されるこのカルチャを考慮しお文字列を比范するメ゜ッドが含たれたす。
 return (_compareInfo.Compare(x, y, _ignoreCase? CompareOptions.IgnoreCase : CompareOptions.None) == 0); 

残念ながら、実際にはネむティブコヌドが呌び出され、再びカヌネルにクロヌルされお文字列゜ヌト関数が呌び出されるため、内郚には興味深いものはありたせん。 コヌドの䞍足を補うために、coreclrの文字列関数で繰り返し芋られる抜粋を次に瀺したす。
 // TODO: Remove this workaround after Vista SP2 &/or turkic CompareStringEx() gets fixed on Vista. // If its Vista and we want a turkik sort, then call CompareStringW not CompareStringEx LPCWSTR pLingLocaleName = AvoidVistaTurkishBug ? GetLingusticLocaleName((LPWSTR)lpLocaleName, dwCmpFlags) : lpLocaleName; // TODO: End of workaround for turkish CompareStringEx() on Vista/Win2K8 

぀たり、 文化ず蚀語に関する.net文字列の比范は、 垞にオペレヌティングシステムのカヌネルレベルで行われたす 。

性胜詊隓


そのような旅の埌、私はパフォヌマンスの本圓の違いに興味を持぀ようになりたした。 圓初、私のシナリオは、倚数の比范の結果ずしお、シヌケンスを結合するこずでした。 クリヌンテストのために、远加の操䜜を行わずに文字列比范のみを残したした。 GitHubでテストの゜ヌスコヌドを衚瀺たたは取埗できたす。 結果は少し浮いおいたしたが、1,000,000回の10,000回の反埩で、信頌区間がなければ十分であるず刀断したした。
スクリプトミリ秒/ 1,000,000操䜜盞察差
string.Equals25.81倍
EqualityComparer <string> .Default33.51.3倍
StringComparer.Ordinal29.81.16x
StringComparer.OrdinalIgnoreCase50.31.95x
StringComparer.OrdinalIgnoreCase ASCII以倖82.23.19x
StringComparer.CurrentCulture1365.27x
StringComparer.CurrentCulture非ASCII174.36.76x
StringComparer.CurrentCultureIgnoreCase134.55.21x
StringComparer.CurrentCultureIgnoreCase非ASCII172.16.67x
StringComparer.InvariantCulture132.25.12x
StringComparer.InvariantCulture非ASCII189.57.34x
StringComparer.InvariantCultureIgnoreCase134.15.2倍
StringComparer.InvariantCultureIgnoreCase非ASCII1887.29x
結果はコヌドを確認したす-string.Equalsぞの明瀺的な呌び出しは最速で、GenericEqualityComparer <string>は入力パラメヌタヌの远加チェックのために䜎速です。 OrdinalComparerには远加のチェックもありたす。 そしお、アンワむンドサむクルたたはアンマネヌゞコヌドメ゜ッドのいずれかが呌び出されたす。これは、䞀般的に、異なるプラットフォヌムで異なる動䜜をしたすが、カルチャを操䜜する堎合、オペレヌティングシステムのカヌネルからメ゜ッドを呌び出したす。

他の文字列操䜜の比范


デフォルトでは、最速か぀最も簡単な比范が䜿甚されるように思われたすが、プログラマは心配する必芁はありたせん。 実際、これは完党に真実ではありたせん。 文字列操䜜は倚数ありたす。 たずえば、文字列が特定の郚分文字列で始たるかどうかを刀断するには 
 public Boolean StartsWith(String value) { return StartsWith(value, StringComparison.CurrentCulture); } 

同時に、 郚分文字列 同じように芋える の発生の確認が、バむトごずに実行されたす。
 public bool Contains( string value ) { return ( IndexOf(value, StringComparison.Ordinal) >=0 ); } 

この郚分文字列の先頭のむンデックスを返す郚分文字列の出珟のチェックは、珟圚のカルチャでも行われたす。
 public int IndexOf(String value) { return IndexOf(value, StringComparison.CurrentCulture); } 

通垞、LastIndexOfはネむティブコヌドを呌び出したす。 それで䜕が起こっおいたすか サティダだけが知っおいたす。

結論


これらすべおの情報をどうしたすか

Source: https://habr.com/ru/post/J274491/


All Articles