2014-01-13 27 views
25

这是我直到今天才注意到的东西。显然,当执行基于相等的操作时,大量使用的元组类(如Tuple<T>,Tuple<T1, T2>等)的.NET实现会导致对值类型的装箱惩罚.NET Tuple和Equals性能

这里是怎么类框架中的一种实现(通过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; 
    } 
} 

我看到的问题是它会导致一个两级装箱拆箱,说了Equals电话,一个,在comparer.Equals哪个方块的物品,两个,EqualityComparer<object>调用非通用Equals,这反过来将在内部不得不将物品解除原始类型。

而是他们为什么不这样做:

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中的元组类。我在其中一个字典中使用元组类型作为关键字。

是否有任何理由为什么这必须如第一个代码所示的那样实施?在这种情况下,使用这个类有点令人沮丧。

我不认为代码重构和非重复数据应该是主要关注的问题。同样的非通用/装箱实施也落后于IStructuralComparable,但由于IStructuralComparable.CompareTo较少使用,所以它经常不是问题。


我与基准第三种方法是征税还是少,像这样(只要领)上面的两种方法:

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); 
} 

的一对夫妇Tuple<DateTime, DateTime>领域一百万Equals电话。这是结果:

1的方法(原.NET实现) - 310毫秒

第二个方法 - 60毫秒

第三届方法 - 130毫秒

默认实现比最佳解决方案慢大约4-5倍。

+1

我没有看到任何理由为什么'IEqualityComparer '被使用过,但我并不是说没有。但你仍然可以做得更好一点:http://pastebin.com/tNA2FYjq – MarcinJuraszek

+1

@MarcinJuraszek这样更好吗? '元组<,>'实现'IStructuralEquatable',它具有这些定义'bool Equals(object other,IEqualityComparer comparer); int GetHashCode(IEqualityComparer comparer);' – nawfal

+1

“...多使用元组类...”。有你的问题! – Gusdor

回答

10

你想知道是否必须以这种方式实施。总之,我会说不:有许多功能相当的实现。

但是,为什么现有的实施方式使EqualityComparer<object>.Default这样明确的使用?这可能只是一个人写的,这个人在精神上为“错误”进行了优化,或者至少与你在内部循环中的速度场景不同。根据他们的基准,它可能看起来是'正确'的东西。

但是什么样的基准场景会导致他们做出这样的选择?那么他们所针对的优化似乎是针对EqualityComparer类模板实例的最小数量进行优化。他们可能会选择这个,因为模板实例化会带来内存或加载时间成本。如果是这样,我们可以猜测他们的基准测试场景可能基于应用启动时间或内存使用情况,而不是一些严格的循环场景。

下面是支持理论的一个知识点(通过使用确认偏倚发现:) - 如果T是结构,EqualityComparer实现方法体不能共享。从http://blogs.microsoft.co.il/sasha/2012/09/18/runtime-representation-of-genericspart-2/

当CLR需要创建一个封闭泛型类型的实例摘录, 如表的基础上, 开放式它创建一个方法表和EEClass。与往常一样,方法表包含方法指针,这些指针由JIT编译器随时编译。但是,这里有一个关键优化:可以共享具有引用类型参数的封闭泛型 类型的编译方法体。 [...] 相同的 想法不适用于值类型。例如,当T很长时, 赋值语句items [size] = item需要使用不同的 指令,因为必须复制8个字节而不是4个。更大的值类型甚至可能需要多个指令;等等。

+0

对其他可能的考虑很好的答案。我现在会接受这个。 – nawfal

+0

@nawfal谢谢!我很开心研究这个! –