Подтвердить что ты не робот

Производительность ConcurrentHashmap против HashMap

Как работает ConcurrentHashMap по сравнению с HashMap, особенно с функцией .get() (меня особенно интересует только несколько элементов в диапазоне от 0-5000)?

Есть ли причина не использовать ConcurrentHashMap вместо HashMap?

(Я знаю, что пустые значения недопустимы)

Обновление

просто для уточнения, очевидно, что производительность в случае фактического одновременного доступа будет страдать, но как сравнивает производительность в случае отсутствия параллельного доступа?

4b9b3361

Ответ 1

Я был очень удивлен, увидев, что эта тема настолько старая, и пока никто не дал никаких тестов по этому делу. Используя ScalaMeter, я создал тесты add, get и remove для HashMap и ConcurrentHashMap в двух сценариях:

  • с использованием одного потока
  • используя столько потоков, сколько я имею доступные ядра. Обратите внимание, что поскольку HashMap не является потокобезопасным, я просто создал отдельный HashMap для каждого потока, но использовал один, общий ConcurrentHashMap.

Код доступен в моем репозитории.

Результаты следующие:

  • Ось X (размер) представляет количество элементов, записанных на карту (ы)
  • Ось Y (значение) представляет время в миллисекундах

Добавить метод Получить метод Удалить метод

Резюме

  • Если вы хотите как можно быстрее работать с вашими данными, используйте все доступные потоки. Это кажется очевидным, каждый поток имеет 1/nth полной работы.

  • Если вы выбираете один доступ к потоку, используйте HashMap, он просто быстрее. Для метода add он даже эффективнее на 3 раза. Только get быстрее на ConcurrentHashMap, но не много.

  • При работе с ConcurrentHashMap со многими потоками аналогично эффективно работать на отдельном HashMaps для каждого потока. Поэтому нет необходимости разбивать данные в разных структурах.

Подводя итог, производительность для ConcurrentHashMap хуже, когда вы используете один поток, но добавление большего количества потоков для выполнения работы, безусловно, ускорит процесс.

Тестирование платформы

AMD FX6100, 16GB Ram
Xubuntu 16.04, обновление Oracle JDK 8 91, Scala 2.11.8

Ответ 2

Безопасность потоков - сложный вопрос. Если вы хотите сделать поток объектов безопасным, сделайте это сознательно и запишите этот выбор. Люди, которые используют ваш класс, будут благодарны вам, если это потокобезопасность, когда это упростит их использование, но они будут проклинать вас, если объект, который когда-то был потокобезопасным, становится не таким в будущей версии. Безопасность нитей, хотя и очень хорошая, не только для Рождества!

Итак, теперь на ваш вопрос:

ConcurrentHashMap (по крайней мере, в Sun current implementation) работает путем деления базовой карты на несколько отдельных ковшей. Получение элемента не требует какой-либо блокировки как таковой, но использует атомные/летучие операции, что подразумевает барьер памяти (потенциально очень дорогостоящий и мешающий другим возможным оптимизациям).

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

Как решить, какую реализацию использовать, выбор, вероятно, простой.

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

Если это локальная переменная, то, скорее всего, достаточно HashMap - если вы не знаете, что ссылки на объект могут просачиваться в другой поток. Посредством кодирования интерфейса карты вы позволяете себе легко изменить его, если вы обнаружите проблему.

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

Если вы знаете, что это поле экземпляра является единственной причиной, по которой класс не является потокобезопасным, и готовы жить с ограничениями, которые подразумевает обещающая безопасность потоков, тогда используйте ConcurrentHashMap, если тестирование не показывает значительных последствий для производительности. В этом случае вы можете позволить пользователю класса выбрать поточно-безопасную версию объекта, возможно, используя другой метод factory.

В любом случае документируйте класс как безопасный поток (или условно потокобезопасный), чтобы люди, которые используют ваш класс, знали, что они могут использовать объекты в нескольких потоках, а люди, которые редактируют ваш класс, знают, что они должны поддерживать безопасность потоков в будущем.

Ответ 3

Я бы порекомендовал вам его измерить, поскольку (по одной причине) может существовать некоторая зависимость от хеширующего распределения тех объектов, которые вы храните.

Ответ 4

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

Параллельный hashmap использует блокировку блокировки и блокирует только те элементы, которые подвержены определенной блокировке. Если вы работаете на современном vm, таком как точка доступа, vm попытается использовать смещение блокировки, грубей и эллипс, если это возможно, поэтому вы будете платить штраф за блокировки, когда вам это действительно нужно.

Таким образом, если ваша карта будет подключаться к параллельным потокам, и вам необходимо гарантировать согласованное представление состояния, используйте параллельный хэш файл.

Ответ 5

В случае хэш-таблицы из 1000 элементов с использованием 10 блокировок для всей таблицы сохраняется почти половина времени, когда вставляет 10000 потоков, и из него удаляется 10000 потоков.

Интересная разница во времени выполнения здесь

Всегда используйте параллельную структуру данных. за исключением случаев, когда обратная сторона полосы (упомянутая ниже) становится частой операцией. В таком случае вам придется приобретать все замки? Я читал, что лучшие способы сделать это - это рекурсия.

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

Нижняя сторона блокировки - трудно получить состояние структуры данных, на которое влияет чередование. В примере размер таблицы или попытка перечислить/перечислить всю таблицу могут быть громоздкими, так как нам нужно получить все полосатые блокировки.

Ответ 6

Какой ответ вы ожидаете здесь?

Очевидно, что будет зависеть от количества чтений, происходящих одновременно с записью и длительностью нормальной записи карты в операции записи в вашем приложении (и независимо от того, будет использовать метод putIfAbsent на ConcurrentMap). Любой бенчмарк будет в значительной степени бессмысленным.

Ответ 7

Не понятно, что вы имеете в виду. Если вам нужна безопасность потоков, у вас почти нет выбора - только ConcurrentHashMap. И у него определенно есть штрафы за производительность/память в вызове get() - доступ к изменчивым переменным и блокировка, если вам не повезло.