Протестировал кое какие нюансы с GetHashCode с целью оптимизации участка, где много обращений к Dictionary — получение значения по ключу, добавление элементов по ключу и тд.
Получилось чтото непонятное.
Единицы измерения не важны, получены с пом. QueryPerformanceCounter, кол-во запусков контрольных участков >1000000
По результатам выходит что дефолтный метод работает медленнее чем он же унаследованый
Унаследованый кешированый работает намного быстрее, следовательно дефолтный ничего не кеширует и вычисляет значение постоянно
Вопрос 1 — почему дефолтный работает медленно, если коде ротора вроде как есть кеширование значения в дефолтной реализации ?
Вопрос 2 — почему метод работает примерно так же как и с кешированием, хотя в коде для строки нет никакого кеширования ?.
P.S. 4.0 дефотный работает чуть быстрее чем 3.5, процентов на 10
Тесты, классы и вычисление хешкода для строки
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override unsafe int GetHashCode()
{
fixed (char* str = ((char*) this))
{
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*) chPtr;
for (int i = this.Length; i > 0; i -= 4)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
if (i <= 2)
{
break;
}
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
numPtr += 2;
}
return (num + (num2 * 0x5d588b65));
}
}
[NamedTest("GetHashCode default")]
private static void Test20()
{
object o = new HashCodeWrapper(new object());
for (int j = 0; j < Count; j++)
{
i ^= o.GetHashCode();
}
}
[NamedTest("GetHashCode from string")]
private static void Test21()
{
object s = new HashCodeWrapper("345");
for (int j = 0; j < Count; j++)
{
i ^= s.GetHashCode();
}
}
[NamedTest("GetHashCode overrided, fixed value")]
private static void Test22()
{
object s = new HashCodeWrapper(new Suxx2(123));
for (int j = 0; j < Count; j++)
{
i ^= s.GetHashCode();
}
}
[NamedTest("GetHashCode inherited default")]
private static void Test23()
{
object s = new HashCodeWrapper(new Suxx());
for (int j = 0; j < Count; j++)
{
i ^= s.GetHashCode();
}
}
[NamedTest("GetHashCode cashed default")]
private static void Test24()
{
object s = new HashCodeWrapper(new Suxx3());
for (int j = 0; j < Count; j++)
{
i ^= s.GetHashCode();
}
}
class Suxx
{
private RectangleF rectf;
private readonly int _hashCode;
}
class Suxx3
{
private RectangleF rectf;
private int _hashCode = 0;
public override int GetHashCode()
{
if (_hashCode == 0)
_hashCode = base.GetHashCode();
return _hashCode;
}
}
class Suxx2
{
private RectangleF rectf;
private readonly int _hashCode;
public Suxx2(int hashCode)
{
_hashCode = hashCode;
}
public override int GetHashCode()
{
return _hashCode;
}
}
class SuxxD
{
private RectangleD rectd;
}
struct RectangleD
{
public double X;
public double Y;
public double Width;
public double Height;
}
class HashCodeWrapper
{
private readonly object _o;
public HashCodeWrapper(object o)
{
_o = o;
}
public override int GetHashCode()
{
return _o.GetHashCode();
}
}
Здравствуйте, Ikemefula, Вы писали:
I>Вопрос 1 — почему дефолтный работает медленно, если коде ротора вроде как есть кеширование значения в дефолтной реализации ?
Вы время на кеширование в своих тестах тоже замеряете или оно исключается из финальных результатов?
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, TK, Вы писали:
I>>Вопрос 1 — почему дефолтный работает медленно, если коде ротора вроде как есть кеширование значения в дефолтной реализации ?
TK>Вы время на кеширование в своих тестах тоже замеряете или оно исключается из финальных результатов?
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, Ikemefula, Вы писали:
I>Если у кого есть желание, вот файл для запуска. Буду весьма признателен за помощь !
Здравствуйте, Ikemefula, Вы писали:
I>Это вобщем то и ожидалось. Что у тебя за проц, частота, ядра, кеш, память, частота шины, версия фреймворка, системы ?
Intel Core 2 Duo E6600, 2,4 GHz, 2 ядра, 4Mb L2, Bus 266MHz, не разогнанный
4 X PC2-6400(400MHz),
Win7
.NET FW 4
I>Не ясно только, как GetHashCode у строки получается таким шустрым.
Похоже на то что GetHashCode у object-а медленный. Сделай строку посерьезнее...
Здравствуйте, samius, Вы писали:
S>Intel Core 2 Duo E6600, 2,4 GHz, 2 ядра, 4Mb L2, Bus 266MHz, не разогнанный S>4 X PC2-6400(400MHz), S>Win7 S>.NET FW 4
Спасибо. Я проверял на трех компах, расклад получается примерно один и тот же, дефолтная реализация в три-пять раз медленнее простого вычисления.
I>>Не ясно только, как GetHashCode у строки получается таким шустрым.
S>Похоже на то что GetHashCode у object-а медленный. Сделай строку посерьезнее...
Здравствуйте, Ikemefula, Вы писали:
I>Протестировал кое какие нюансы с GetHashCode с целью оптимизации участка, где много обращений к Dictionary — получение значения по ключу, добавление элементов по ключу и тд. I>Получилось чтото непонятное.
I>
Все непонятные вещи вроде стали понятными — GetHashCode у строки не кешируется, вроде samius подсказал, а я все равно тупил
Соответсвенно нужно быть осторожно с объектами, которые сравниваются по значению, GetHashCode для оных нужно писать очень аккуратно, ибо издержки на вычисления оного кода могут быть практически любыми, вплоть до того, что надобность в Dictionary исчезает
Здравствуйте, Ikemefula, Вы писали:
I>Соответсвенно нужно быть осторожно с объектами, которые сравниваются по значению, GetHashCode для оных нужно писать очень аккуратно, ибо издержки на вычисления оного кода могут быть практически любыми, вплоть до того, что надобность в Dictionary исчезает
Dictionary<TKey,TValue> хэшкод как раз кэширует и вычисляет лишь однажды при каждой операции. Чем GetHashCode можно так нагрузить чтобы аж словарь просел?
Здравствуйте, samius, Вы писали:
S>Dictionary<TKey,TValue> хэшкод как раз кэширует и вычисляет лишь однажды при каждой операции. Чем GetHashCode можно так нагрузить чтобы аж словарь просел?
Словарь кеширует для того, что бы можно было размер внутреннего массива менять.
А вот при обращении например чз ContainsKey у объекта по любому запрашивается хешкод который в этот момент и вычисляется заново.
Вычисление хешкода для строки — долгая операция. Чем длиннее строка, тем меньше преимуществ у Dictionary перед List.
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, samius, Вы писали:
S>>Dictionary<TKey,TValue> хэшкод как раз кэширует и вычисляет лишь однажды при каждой операции. Чем GetHashCode можно так нагрузить чтобы аж словарь просел? I>Словарь кеширует для того, что бы можно было размер внутреннего массива менять.
I>А вот при обращении например чз ContainsKey у объекта по любому запрашивается хешкод который в этот момент и вычисляется заново.
Да, я и написал что однажды на операцию.
I>Вычисление хешкода для строки — долгая операция. Чем длиннее строка, тем меньше преимуществ у Dictionary перед List.
Сравнение строк тоже небыстрая операция, еще неизвестно, что дороже для длинной строки, взять ее хэшкод или сравнить ее с идентичной, либо отличающейся в хвосте...
Т.е. либо раз взять хэшкод и несколько раз сравнить (в среднем нужно одно сравнение) при работе со словарем, либо n раз сравнивать при работе со списком. Притом при нахождении идентичной строки однажды случится полное сравнение и в том и в другом случае. Получается что одно взятие хэшкода против O(n) неполных сравнений.
Здравствуйте, Ikemefula, Вы писали:
I>Если у кого есть желание, вот файл для запуска. Буду весьма признателен за помощь !
I>
I> const int Count = 3000000;
...
I> [NamedTest("GetHashCode default, fill, extract")]
I> private static void Test15()
I> {
I> Dictionary<object,object > hash = new Dictionary<object, object>(); // <- Вот тут очень не помешает Count в скобках.
I> List<Suxx> list = new List<Suxx>(Count);
...
I> }
I>}
I>
Небольшой комментарий по коду, не относящийся непосредственно к хешированию:
при инициализации Dictionary и HashTable тоже крайне желательно проставлять Capacity, как и для списков.
Иначе в процессе добавления элементов в эти контейнеры их внутренняя хеш-таблица будет несколько раз заново перестраиваться.
Здравствуйте, samius, Вы писали:
S>Т.е. либо раз взять хэшкод и несколько раз сравнить (в среднем нужно одно сравнение) при работе со словарем, либо n раз сравнивать при работе со списком. Притом при нахождении идентичной строки однажды случится полное сравнение и в том и в другом случае. Получается что одно взятие хэшкода против O(n) неполных сравнений.
Это и ежу понятно.
Речь о том, что GetHashCode медленнее намного, чем Equals.
На длинных строках список из двух-трех десятков строк будет работать быстрее нежели Dictionary.
А на огромных строках возможно и сотня элементов в списке будет проверяться быстрее.
Здравствуйте, Mr.Cat, Вы писали:
MC>…Интересно было бы посмотреть, что там внутри у Object.GetHashCode().
А зачем? В тех местах, где производительность GetHashCode() является значимой (или хотя бы мало-мальски интересной) величиной, необходимо этот метод переопределить (или определить свой провайдер или equality comparer). "Дефолтовая" же реализация имеет право (и об этом прямо сказано в документации) может меняться от версии к версии.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Ikemefula, Вы писали:
I>Речь о том, что GetHashCode медленнее намного, чем Equals.
В общем случае — да (и понятно почему). Хотя, ничто не мешает для конкретного
случая написать свою упрощённую хеш-функцию. Например, для небольшого набора строк,
вариантных по длине, даже примитивнейший хеш — длина строки — будет давать
приемлемо низкий процент коллизий.
I>На длинных строках список из двух-трех десятков строк будет работать быстрее нежели Dictionary. I>А на огромных строках возможно и сотня элементов в списке будет проверяться быстрее.
Логично. Чем сложнее подход, тем, в общем случае, на более "крутые" случаи он рассчитан.
К примеру, простейшая сортировка вставками со средней сложностью O(N^2) на небольших входных
массивах легко уделает более громоздкий квиксорт с его средней сложностью O(N*logN).
Здравствуйте, _FRED_, Вы писали: _FR>А зачем?
А зачем вообще знание того, как внутри работает тот или иной метод? Вот, например, строка не запоминает свой хэш — это по-твоему полезное знание? Кому-то нравится программировать под черный ящик, кому-то нет.
_FR>В тех местах, где производительность GetHashCode() является значимой (или хотя бы мало-мальски интересной) величиной, необходимо этот метод переопределить (или определить свой провайдер или equality comparer).
Капитан, вы?
_FR>"Дефолтовая" же реализация имеет право (и об этом прямо сказано в документации) может меняться от версии к версии.
И что дальше?
PS: Если в msdn напишут, что надо в окно прыгнуть — ты прыгнешь?
Здравствуйте, Mr.Cat, Вы писали:
_FR>>А зачем? MC>А зачем вообще знание того, как внутри работает тот или иной метод?
Сказано же, что реализация может меняться (и меняется) от версии к версии. Вам все варианты "как" интересны или любой подойдёт? Если бы было хоть чуточку интересно, то нашли бы уже первую подсказку. А дальше — как в романах Дэна Брауна
MC>Вот, например, строка не запоминает свой хэш — это по-твоему полезное знание? Кому-то нравится программировать под черный ящик, кому-то нет.
Например, для строки хеш часто вообще неправильно (точнее, не лучшим, совсем не рациональным, грозящим многими коллизиями, образом) высчитывается. Но строка — это не Object, GetHashCode() которой настоятельно рекомендуют переопределять. Поэтому сравнение знания о том, как считается хэш у строки и у Object не является корректным.
В данном конкретном случае — дефолтовая реализация есть ни что иное как совершенно бесполезное знание. Да вот и коментарии об этом же говорят:
// Objects (& especially value classes) should override this method.
Желание узнать эту реализацию сродни желанию узнать, видит ли белый коридор человек перед смертью.
_FR>>В тех местах, где производительность GetHashCode() является значимой (или хотя бы мало-мальски интересной) величиной, необходимо этот метод переопределить (или определить свой провайдер или equality comparer). MC>Капитан, вы?
Спасибо вам за высказывание вашего мнения о моей личности. Для меня это очень ценно.
_FR>>"Дефолтовая" же реализация имеет право (и об этом прямо сказано в документации) может меняться от версии к версии. MC>И что дальше? MC>PS: Если в msdn напишут, что надо в окно прыгнуть — ты прыгнешь?
А вам непременно надо знать, что испытывают гомосеки? Тогда в чём заключается сложность? Запускайте программу, подключайте отладчик и изучайте. Не думаю, что ответ на ваш вопрос интересен на столько, что бы найти его.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали: _FR>http://stackoverflow.com/questions/720177/default-implementation-for-object-gethashcode
На ротор еще ТС ссылался, но по нему пока непонятно, почему в одном и том же коде у кого-то хэш кэшируется эффективно, а у кого-то нет. Есть какие-нить предположения?
MC>>Вот, например, строка не запоминает свой хэш — это по-твоему полезное знание? _FR>Например ... не является корректным.
Будем считать, что это было длинное "да"?
_FR>
// Objects (& especially value classes) should override this method.
Ну вот ТС наоборот (вроде как) не нужно переопределять. Не догма же.
MC>>PS: Если в msdn напишут, что надо в окно прыгнуть — ты прыгнешь? _FR>А вам непременно надо знать, что испытывают гомосеки?
Эээ?