Этого я не заметил до сегодняшнего дня. По-видимому, .NET-реализация сильно используемых классов кортежей (Tuple<T>
, Tuple<T1, T2>
и т. Д.) Вызывает штрафы за бокс для типов значений, когда выполняются операции, основанные на равенстве.Производительность 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
вызовов 1000000. Это результат:
первый подход (оригинальная реализация .NET) - 310 мс
второй подход - 60 мс
третий подход - 130 мс
Реализация по умолчанию примерно в 4-5 раз медленнее, чем оптимальное решение.
Я не вижу причин, почему использовался 'IEqualityComparer
@MarcinJuraszek Как это лучше? 'Tuple <,>' реализует 'IStructuralEquatable', который имеет эти определения' bool Equals (object other, IEqualityComparer comparer); int GetHashCode (сравнитель IEqualityComparer); ' – nawfal
" ... много используемых классов Tuple ... ". Там твоя проблема! – Gusdor