Словарь производительности C #: по умолчанию строка Comparer GetHashCode () выделяет память в нарушение правил, тем самым снижая производительность? - PullRequest
16 голосов
/ 30 августа 2011

Существует установленное правило , что получение хеш-кода не должно выделять память, поскольку это негативно повлияет на поиск в хеш-таблице, вызвав сборщик мусора.

Тем не менее, именно этот сбой я вижу в профиле моего приложения, в котором используется 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
        • 0,5% Сравнить
          • 0,5% [сборка мусора]

Порядковый:

  • 100% TryGetValue
    • 100% FindEntry
      • 47.22% CultureAwareComparer.Equals
        • 47,22% [Сборка мусора]

Также, как представляется, резко сократились общие затраты времени в TryGetValue. Я не был осторожен, чтобы удостовериться, что все остальное было равным, но это составило 46 секунд из 10-минутного стресс-теста в первом прогоне, а в цикле пробега - 252 миллисекунды. Считайте, что это неподтвержденная, а не ожидаемая относительная стоимость.

Кажется, что вся стоимость хеша, которая раньше составляла 99 +% от стоимости, теперь настолько «бесплатна», что даже не отображается в профилировщике, который, я думаю, работает в режиме выборки.

Полагаю, на этой секунде слово на улице, что вам следует использовать порядковое сравнение.

Я до сих пор не могу доказать, почему стоимость GC так сильно влияет на результат первого профиля, ноИсходя из комментариев, приведенных ниже, я полагаю, что должен полагать, что он НЕ выделяет управляемую память кучи, но из-за того, что он медленный, он, как правило, является функцией "случайного" GCed других действий в других потоках, поскольку этот процесс действительно использует сервер.mode gc.

Может быть, это указывает на то, что этот узкий цикл имеет тенденцию совпадать с кодом, удовлетворяющим распределению, где-то еще.

1 Ответ

10 голосов
/ 31 августа 2011

По умолчанию при использовании клавиш string используется string.GetHashCode().Этот метод не выделяет никакой памяти в куче и должен быть довольно быстрым.

Но так как вы используете регистр игнорирования, вместо него используется CultureAwareComparer.GetHashCode().Этот метод вызывает (как видно из результатов вашего профиля) CompareInfo.GetHashCodeOfString(), что, в свою очередь, вызывает неуправляемую функцию InternalGetGlobalizedHashCode().Ни один из двух управляемых методов не выделяет кучу (как вы можете видеть, если посмотреть на них в декомпиляторе).Я не могу сказать, что делает InternalGetGlobalizedHashCode(), но, поскольку он неуправляем, я сомневаюсь, что он делает какие-либо выделения в управляемой куче.В любом случае, это должно быть намного сложнее, чем вычисление хеш-кода по умолчанию, тем более что оно учитывает особенности культуры и должно учитывать такие проблемы, как Turkish İ .

Это означает, что у вас, вероятно, есть какой-то другой код, который выделяет память в куче, что вызывает сборку мусора.

И если вы хотите добиться максимальной производительности, вам следует избегать «игнорировать регистр», особенноего культурно-ориентированные варианты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...