2014-01-13 9 views
25

これは私が今日まで気付いていなかったものです。明らかに、多用されたタプルクラス(Tuple<T>Tuple<T1, T2>など)の.NET実装は、等価ベースの操作が実行されるときに値タイプのボクシングペナルティを発生させます。ここで.NETタプルと同等のパフォーマンス

は、クラスは一種のフレームワーク(ILSpyを介してソース)に実装されている方法です。

public class Tuple<T1, T2> : IStructuralEquatable 
{ 
    public T1 Item1 { get; private set; } 
    public T2 Item2 { get; private set; } 

    public Tuple(T1 item1, T2 item2) 
    { 
     this.Item1 = item1; 
     this.Item2 = item2; 
    } 

    public override bool Equals(object obj) 
    { 
     return this.Equals(obj, EqualityComparer<object>.Default); 
    } 

    public override int GetHashCode() 
    { 
     return this.GetHashCode(EqualityComparer<object>.Default); 
    } 

    public bool Equals(object obj, IEqualityComparer comparer) 
    { 
     if (obj == null) 
     { 
      return false; 
     } 

     var tuple = obj as Tuple<T1, T2>; 
     return tuple != null 
      && comparer.Equals(this.Item1, tuple.Item1) 
      && comparer.Equals(this.Item2, tuple.Item2); 
    } 

    public int GetHashCode(IEqualityComparer comparer) 
    { 
     int h1 = comparer.GetHashCode(this.Item1); 
     int h2 = comparer.GetHashCode(this.Item2); 

     return (h1 << 5) + h1^h2; 
    } 
} 

私が見る問題はそれが2段のボクシングアンボクシングの原因である、Equalsコール、1のために言って、 comparer.Equalsでは、2つのアイテム、EqualityComparer<object>非汎用Equalsを呼び出し、内部でアイテムを元のタイプにunboxする必要があります。私は平等を見て驚きました

public override bool Equals(object obj) 
{ 
    var tuple = obj as Tuple<T1, T2>; 
    return tuple != null 
     && EqualityComparer<T1>.Default.Equals(this.Item1, tuple.Item1) 
     && EqualityComparer<T2>.Default.Equals(this.Item2, tuple.Item2); 
} 

public override int GetHashCode() 
{ 
    int h1 = EqualityComparer<T1>.Default.GetHashCode(this.Item1); 
    int h2 = EqualityComparer<T2>.Default.GetHashCode(this.Item2); 

    return (h1 << 5) + h1^h2; 
} 

public bool Equals(object obj, IEqualityComparer comparer) 
{ 
    var tuple = obj as Tuple<T1, T2>; 
    return tuple != null 
     && comparer.Equals(this.Item1, tuple.Item1) 
     && comparer.Equals(this.Item2, tuple.Item2); 
} 

public int GetHashCode(IEqualityComparer comparer) 
{ 
    int h1 = comparer.GetHashCode(this.Item1); 
    int h2 = comparer.GetHashCode(this.Item2); 

    return (h1 << 5) + h1^h2; 
} 

は、.NETタプルクラスでこの方法を実装:

は代わりになぜ彼らのような何かをしないだろう。私は辞書の1つでタプルタイプをキーとして使用していました。

最初のコードに示されているようにこれを実装しなければならない理由はありますか?その場合、このクラスを利用するのはやめてください。

私は、コードのリファクタリングと重複しないデータが大きな問題であったはずだと思います。同じ非ジェネリック/ボクシングの実装はIStructuralComparableにもなりましたが、IStructuralComparable.CompareToはあまり使われていないので、よく問題になりません。 Tuple<DateTime, DateTime>フィールド1000000回のEqualsコールのカップルのため

public override bool Equals(object obj) 
{ 
    return this.Equals(obj, EqualityComparer<T1>.Default, EqualityComparer<T2>.Default); 
} 

public bool Equals(object obj, IEqualityComparer comparer) 
{ 
    return this.Equals(obj, comparer, comparer); 
} 

private bool Equals(object obj, IEqualityComparer comparer1, IEqualityComparer comparer2) 
{ 
    var tuple = obj as Tuple<T1, T2>; 
    return tuple != null 
     && comparer1.Equals(this.Item1, tuple.Item1) 
     && comparer2.Equals(this.Item2, tuple.Item2); 
} 


は私はこのようなまだ少ない課税である第三のアプローチは、(のみ必需品)を用いて、上記2つのアプローチベンチマーク。これは結果である:

第1のアプローチ(元の.NET実装) - 310ミリ秒

第2のアプローチを - 60ミリ秒

3アプローチ - デフォルトの実装は130ミリ秒

最適解より約4〜5倍遅くなります。

+1

「IEqualityComparer 」が使用された理由はありませんが、私には何も言われていません。しかし、あなたはまだそれを少しでも良くすることができます:http://pastebin.com/tNA2FYjq – MarcinJuraszek

+1

@MarcinJuraszekそれはどうですか? 'Tuple <,>'はこれらの定義を持つ 'IStructuralEquatable'を実装しています。' bool Equals(object other、IEqualityComparer comparer); int GetHashCode(IEqualityComparer comparer); ' – nawfal

+1

" ...多用されたタプルクラス... "。あなたの問題があります! – Gusdor

答えて

10

あなたはそのように実装する必要があるかどうか疑問に思いました。要するに、私はいいえと言うでしょう:機能的に同等の実装がたくさんあります。

しかし、なぜ既存の実装でこのような明示的な使用が行われていますかEqualityComparer<object>.Default?これは、精神的に「間違っている」ために精神的に最適化を書いた人、または内側のループのあなたのスピードシナリオとは少なくとも異なるもののケースかもしれません。彼らのベンチマークによって、それは「正しい」ものに見えるかもしれません。

しかし、どのようなベンチマークシナリオを選択することができますか?彼らが目標としている最適化は、EqualityComparerクラスのテンプレートインスタンス化の最小数を最適化することにあるようです。テンプレートインスタンシエーションにはメモリやロード時のコストがかかりますので、これを選択する可能性があります。もしそうなら、我々のベンチマークシナリオは、いくつかの緊密なループシナリオではなく、アプリケーションの起動時またはメモリ使用量に基づいている可能性があると推測できます。

理論をサポートするための知識ポイントが1つあります(確認バイアスを使用して見つかりました:)。Tが構造体の場合はEqualityComparer実装のメソッド本体を共有できません

CLRが閉鎖ジェネリック型のインスタンスを作成する必要がhttp://blogs.microsoft.co.il/sasha/2012/09/18/runtime-representation-of-genericspart-2/から抜粋、 ようなリストとしては、 オープンタイプに基づいて、メソッドテーブルとEEClassを作成します。いつものように、メソッドテーブルにはメソッドポインタが含まれており、 はJITコンパイラによってその場でコンパイルされます。ただし、ここでは という重要な最適化があります。クローズされた汎用の 型のコンパイルされたメソッド本体は、参照型のパラメータを持つ型を共有できます。 [...] 同じ のアイデアは、値の型では機能しません。たとえば、Tが長い場合、 の代入文項目[size] = itemは、 命令とは異なる命令を必要とします。 の値の型も2つ以上の命令を必要とすることさえあります。等々。

+0

可能な他の考慮事項については良い答えです。私はこれを今受け入れます。 – nawfal

+0

@nawfalありがとう!私はこれを研究するのが楽しい! –