我认为标题非常清楚。
我想知道在IEqualityComparer
中使用Dictionary<K,V>
时是否存在一定的效率开销?在提供一个时,它是如何工作的?
由于
答案 0 :(得分:35)
更快吗?
从gamedev的角度来看,如果你的密钥是一个值类型(struct,primitive,enum等),那么提供你自己的EqualityComparer<T>
要快得多 - 因为EqualityComparer<T>.Default
框的事实是值。
作为一个真实的例子,Managed DirectX广告牌样本的运行速度大约是C ++版本的30%;其他所有样品的运行率均在~90%左右。原因是广告牌使用默认比较器进行排序(因此被装箱),因为事实证明,每个帧都会复制4MB的数据。
它是如何运作的?
Dictionary<K,V>
将通过默认构造函数向自己提供EqualityComparer<T>.Default
。默认的相等比较器做的是(基本上,注意发生了多少拳击):
public void GetHashCode(T value)
{
return ((object)value).GetHashCode();
}
public void Equals(T first, T second)
{
return ((object)first).Equals((object)second);
}
我为什么要使用它?
看到这种代码(尝试使用不区分大小写的密钥时)很常见:
var dict = new Dictionary<string, int>();
dict.Add(myParam.ToUpperInvariant(), fooParam);
// ...
var val = dict[myParam.ToUpperInvariant()];
这真的很浪费,最好在构造函数上使用StringComparer:
var dict = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
是否更快(redux)?
在这种特定情况下,速度要快得多,因为序数字符串比较是您可以做的最快的字符串比较类型。一个快速的基准:
static void Main(string[] args)
{
var d1 = new Dictionary<string, int>();
var d2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
d1.Add("FOO", 1);
d2.Add("FOO", 1);
Stopwatch s = new Stopwatch();
s.Start();
RunTest1(d1, "foo");
s.Stop();
Console.WriteLine("ToUpperInvariant: {0}", s.Elapsed);
s.Reset();
s.Start();
RunTest2(d2, "foo");
s.Stop();
Console.WriteLine("OrdinalIgnoreCase: {0}", s.Elapsed);
Console.ReadLine();
}
static void RunTest1(Dictionary<string, int> values, string val)
{
for (var i = 0; i < 10000000; i++)
{
values[val.ToUpperInvariant()] = values[val.ToUpperInvariant()];
}
}
static void RunTest2(Dictionary<string, int> values, string val)
{
for (var i = 0; i < 10000000; i++)
{
values[val] = values[val];
}
}
// ToUpperInvariant: 00:00:04.5084119
// OrdinalIgnoreCase: 00:00:02.1211549
// 2x faster.
<强>预订强>
可以通过在结构上实现接口(例如IEquatable<T>
)来消除装箱开销。但是,在这些情况下拳击发生时有很多令人惊讶的规则,因此我建议使用配对界面(例如在这种情况下为IEqualityComparer<T>
),如果可能的话。
答案 1 :(得分:20)
Jonathan有great answer指出如何使用正确的相等比较器改善性能,Jon在his great answer中澄清Dictionary<K, V>
总是使用IEqualityComparer<T>
EqualityComparer<T>.Default
IEquatable<T>
{1}}除非您指定另一个。
我想谈的是使用默认的相等比较器时EqualityComparer<T>.Default
接口的作用。
当您调用CreateComparer
时,它会使用缓存的比较器(如果有)。如果这是您第一次使用该类型的默认相等比较器,它会调用一个名为CreateComparer
的方法并将结果缓存以供以后使用。以下是.NET 4.5中var t = (RuntimeType)typeof(T);
// If T is byte,
// return a ByteEqualityComparer.
// If T implements IEquatable<T>,
if (typeof(IEquatable<T>).IsAssignableFrom(t))
return (EqualityComparer<T>)
RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(
(RuntimeType)typeof(GenericEqualityComparer<int>), t);
// If T is a Nullable<U> where U implements IEquatable<U>,
// return a NullableEqualityComparer<U>
// If T is an int-based Enum,
// return an EnumEqualityComparer<T>
// Otherwise return an ObjectEqualityComparer<T>
的修剪和简化实现:
IEquatable<T>
但是对于实现GenericEqualityComparer<T>
的类型意味着什么呢?
这里是internal class GenericEqualityComparer<T> : EqualityComparer<T>
where T: IEquatable<T>
// ...
:
where T : IEquatable<T>
魔法发生在泛型类型约束(T
部分)中,因为如果(IEquatable<T>)T
是值类型,则使用它 not 涉及装箱,不像{{1这里正在发生,这是泛型的主要好处。
所以,假设我们想要一个将整数映射到字符串的字典 如果我们使用默认构造函数初始化一个会发生什么?
var dict = new Dictionary<int, string>();
EqualityComparer<T>.Default
,除非我们指定另一个。EqualityComparer<int>.Default
将检查int是否实现IEquatable<int>
。int
(Int32
)实施了IEquatable<Int32>
。对EqualityComparer<T>.Default
的第一次调用将创建并缓存一个通用的比较器,这可能需要一点点但是在初始化时,它是一个强类型GenericEqualityComparer<T>
并且使用它将不会导致装箱或不必要的开销。
对EqualityComparer<T>.Default
的所有后续调用都将返回缓存的比较器,这意味着初始化的开销仅为每种类型的一次性。
那么这一切意味着什么?
T
未实现IEquatable<T>
或,则实施自定义相等比较器IEquatable<T>
的实施不执行你想要它做什么。obj1.Equals(obj2)
没有给你想要的结果。)在Jonathan的回答中使用StringComparer
是一个很好的例子,说明为什么要指定自定义相等比较器。
T
实施IEquatable<T>
和 IEquatable<T>
的实施,为了提高效果,请不要实施自定义相等比较器做你想做的事。obj1.Equals(obj2)
为您提供所需的结果。)在后一种情况下,请改用EqualityComparer<T>.Default
。
答案 2 :(得分:8)
Dictionary<,>
始终使用IEqualityComparer<TKey>
- 如果您未通过,则使用EqualityComparer<T>.Default
。因此,效率取决于您的实施与EqualityComparer<T>.Default
(仅委托给Equals
和GetHashCode
)进行比较的效率。
答案 3 :(得分:0)
我遇到了一个相同的EqualityComparer
...关键部分GetHashCode
,在定位object[]
并且记录超过20k时生成重复键时遇到了巨大的麻烦..以下是解决方案
public class ObJectArrayEqualityComparer : IEqualityComparer<object[]>
{
public bool Equals(object[] x, object[] y)
{
if (x.Length != y.Length)
{
return false;
}
for (int i = 0; i < x.Length; i++)
{
var tempX = x[i];
var tempY = y[i];
if ((tempX==null || tempX ==DBNull.Value)
&& (tempY == null || tempY == DBNull.Value))
{
return true;
}
if (!tempX.Equals(tempY)
&& !System.Collections.StructuralComparisons.StructuralEqualityComparer.Equals(tempX, tempY))
{
return false;
}
}
return true;
}
public int GetHashCode(object[] obj)
{
if (obj.Length == 1)
{
return obj[0].GetHashCode();
}
int result = 0;
for (int i = 0; i < obj.Length; i++)
{
result = result + (obj[i].GetHashCode() * (65 + i));
}
return result;
}
}