Существует установленное правило , что получение хеш-кода не должно выделять память, поскольку это негативно повлияет на поиск в хеш-таблице, вызвав сборщик мусора.
Тем не менее, именно этот сбой я вижу в профиле моего приложения, в котором используется System.Collections.Generic.Dictionary
В глубине очень узкой петли я нашел следующее в результатах своего профилировщика:
- [3.47%] TryGetValue (TKey, TValue &) (... словарь)
- [3.47%] FindEntry (TKey) (... словарь)
- [3.47%] GetHashCode (строка) (System.CultureAwareComparer)
- [3.46%] GetHashCodeOfString (String, CompareOptions) (System.Globalization.CompareInfo)
- [3,39%] [Сборка мусора]
- [0,01%] [Нить приостановлена]
Вот и вся учетная запись поддерева от профилировщика.
Я не опытный специалист в этой специфической работе, поэтому я мог неправильно читать эти чайные листья. Но мне кажется, что GetHashCodeOfString «должен быть» выделяет память и предлагает сборщику мусора прервать мою программу в середине этого цикла. Я хочу ДЕЙСТВИТЕЛЬНО НАСТРОЕН И ТЯЖЕЛЫЙ, и это объясняет ошеломляющее большинство затрат этого цикла.
Кроме того, вот еще одно доказательство того, что этот код выделяет память
Следующим моим шагом будет инициализация словаря с помощью порядкового компаратора и повторный запуск моих тестов.
Но я хочу знать, существует ли существующая мудрость вокруг этой проблемы. Кажется, словари со строковыми ключами являются обычным явлением, и стоимость таких распространенных вещей может быть хорошо изучена. Я обнаружил следующий анализ, но он сосредоточен на фактическом сравнении как причине горя, а не на методе хэш-кода, выделяющем память.
Кто-нибудь может предложить правильный способ использования словаря со строковыми ключами, чтобы избежать этой проблемы?
У меня есть конкретные вопросы:
- Если я использую порядковый компаратор, распределение уйдет?
- Если нет, нужно ли мне писать свой собственный компаратор, и ЭТО заставит выделение ресурсов уйти?
- Если я убью компаратора, могу ли я ожидать реального улучшения в соответствии с рекомендацией MSFT, с которой я начал?
РЕДАКТИРОВАТЬ: Crud, мой плохой, но это не со свойствами сравнения по умолчанию, мы установили ignoreCase. Не уверен, что это повлияет на результаты, но поскольку ignoreCase повлияет на равенство, это должно оказать некоторое влияние на хеш.
ОБНОВЛЕНИЕ: Запустил еще один тест с использованием порядкового компаратора (все еще с IgnoreCase) и преобразовал исходные результаты в 100% стоимость = TryGetValue, чтобы было больше яблок для яблок
Оригинал:
- 100% TryGetValue
- 100% FindEntry
- 99,5% CultureAwareComparer.GetHashCode
- 99,5% CompareInfo.GetHashCodeOfString
- 95,86% [Сборка мусора]
- 3,31% [Тема приостановлена]
- 0,5% CultureAwareComparer.Equals
Порядковый:
- 100% TryGetValue
- 100% FindEntry
- 47.22% CultureAwareComparer.Equals
Также, как представляется, резко сократились общие затраты времени в TryGetValue. Я не был осторожен, чтобы удостовериться, что все остальное было равным, но это составило 46 секунд из 10-минутного стресс-теста в первом прогоне, а в цикле пробега - 252 миллисекунды. Считайте, что это неподтвержденная, а не ожидаемая относительная стоимость.
Кажется, что вся стоимость хеша, которая раньше составляла 99 +% от стоимости, теперь настолько «бесплатна», что даже не отображается в профилировщике, который, я думаю, работает в режиме выборки.
Полагаю, на этой секунде слово на улице, что вам следует использовать порядковое сравнение.
Я до сих пор не могу доказать, почему стоимость GC так сильно влияет на результат первого профиля, ноИсходя из комментариев, приведенных ниже, я полагаю, что должен полагать, что он НЕ выделяет управляемую память кучи, но из-за того, что он медленный, он, как правило, является функцией "случайного" GCed других действий в других потоках, поскольку этот процесс действительно использует сервер.mode gc.
Может быть, это указывает на то, что этот узкий цикл имеет тенденцию совпадать с кодом, удовлетворяющим распределению, где-то еще.