Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
get(char), put(float, long) — все это естьпри попеременных удалениях и вставках разных случайных элементов — k заполненных и (1 — k) ^2 — свободных (все величины — в долях от 1).— тоже взято не с потолка.
Но 0,5 — действительно на глаз. Вроде не очень много, но вроде и не очень мало. Почему, например, не 0,45 или 0,55 — никакого обоснования нет.
Не забывайте, что 3-я и последующие пробы в 2-3 раза дешевле, чем первые 2, потому что для них не надо нацело делить хеш на размер таблицы.
Это одна из причин, почему j.u.HMap может себе позволить класть болт на рекомендации Кнута про простые числа, тупо иметь размер таблицы в 2^N и все еще неплохо работать даже с теми уебищными неоптимальными хэш-функциями, которые пишут в 99.9% случаев (хотя в какой-то из версий авторы-таки решили, что немного подсолить не помешает, и теперь там есть небольшой трюк чтобы сгладить-таки совсем уж плохие варианты). А trove себе такого позволить не может, и мучается с простым размером таблицы, и сопутствующим ему дорогим «честным» делением
Не забывайте, что кроме сравнения хэшей каждая проба требует еще и чтения — т.е. это потенциальный кэш-промах. Это не актуально для линейного хэширования, где высокая локальность, да паттерн прохода по массиву идеально предсказуем, но вполне себе актуально для двойного хэширования, которое используется в trove.
Весьма разумно стараться, чтобы у direct hit было тотальное доминирование по вероятности — собственно, на это и заточен код в trove.
При этом ищут элемент в хеш-таблице, либо для того, чтобы его туда потом вставить, либо чтобы взять значение из параллельного массива (map.get() или set.contains()). В первом случае можно считать, что элемента в таблице, скорее всего, еще нет, а во втором — что он, скорее всего, уже там лежит. В предыдущей реализации Trove в обоих вариантах поиска в первую очередь проверяется, не пуста ли ячейка, и потом, не лежит ли там то, что мы ищем. Хотя при поиске «на взятие» надо в первую очередь проверять, не равен ли текущий элемент искомому (да так и есть).
5-6 проб на поиск, то под этот режим и надо писать код.
Интересный (гипотетический) эффект здесь в том, что код (непосредственно скомпилированные JIT-ом байтики) будет общий, как для тех таблиц, где по-умолчанию load factor = 0.5, так и для ваших, с lf=0.8. При этом статистические свойства условных переходов будут сильно отличаться — а статистика предсказателя ветвлений привязана ведь к коду, а не к данным. Т.е. вы своим выбором нетривиального lf можете сбивать с толку hardware branch predictor, ухудшая производительность как для «своих» таблиц, так и для дефолтных.
порядок веток по вероятности не зависит ни от коэффициента,
хеши в определенном смысле «идеальны» по определению
Множества объектов и мапы объект — примитив/объект все-таки в основном «до кучи» в библиотеке, что называется.
Можно считать, что это «историческое» решение в Trove
Я хочу посмотреть, как соотносятся эффекты большего числа промахов при меньшем коэффициенте
Позитивный момент в том, что порядок веток по вероятности не зависит ни от коэффициента, ни от номера пробы
Ручной unroll цикла пробинга? Интересно, не думал об этом, правда на вскидку не уверен, что что-то даст :)
Именно что «в определенном» — плохой с точки зрения oahm хэш это не только такой, когда все ключи отображаются в 1, но и такой, когда заметная часть используемых в конкретной таблице ключей отображаются в один слот. У примитивов хэши «универсально» хорошие — все множество возможных int-ов равномерно отображается во множество хэшей, да. Но в таблицу-то вы складываете не произвольные int-ы, а какие-то конкретные. И если у вас размер таблицы 17, а кладете вы туда ключи, среди которых, скажем, преобладают ключи вида 17*i+3, то у вас и будет неравномерное распределение ключей по слотам (а не ключей по хэшам). И легко видеть, что это проблема именно конкретной схемы организации хэштаблицы, а не хэша как такового: положи вы тот же набор ключей в j.u.HM с размером 16 — все будет шоколадно.
How caching affects hashing
В чем же тут позитив-то? В предсказании ветвлений важна вероятность неуспешного предсказания — потому что откат выполненных спекуляций может быть весьма дорогим. Если у вас нет сильного доминирования одной ветки над другой — это значит, что либо предсказания вообще не будет (т.е. не будет и упреждающего выполнения — тупо теряем такты), либо предсказание будет часто ошибаться (с сопутствующими пенальти). Даже в ваших идеальных формулах видно, что direct hit доминирует даже над первой пробой с вероятностью только alpha=loadFactor=0.8 в вашем случае. Вряд ли это можно назвать сильным доминированием.
Чем мне не нравится ваше решение: очевидно, что жертвуя всего 20% памяти вы не можете получить какой-то особо большой прирост производительности по сравнению с отсортированным массивом как реперной точкой, ведь бесплатный сыр бывает только в мышеловке. Но проблема в том, что вы получаете непредсказуемую производительность, очень сильно зависящую от расклада ключей. На мой взгляд, большинство бизнес-приложений предпочтут в таких условиях большую стабильность призрачному выигрышу в перформансе.
Что при строго определенном характере ключей и строго определенном размере таблицы, будет много коллизий? Это из серии «лучше быть здоровым и богатым, чем бедным и больным». Один рехеш на новый размер — и все коллизии ушли.
В норме пропусков и отдельных ключей нет, но вообще может быть и случай, близкий к случайным ключам.
Посмотрите же таблицы в статье, там в одном из экспериментов с линейным хэшированием для заполнения 0.6, например, при равномерном распределении ключей среднее число проб 1.75, а при неравномерном урезанном гауссе — 2170. Увеличение в 1000 раз по сравнению с теорией вас не достаточно впечатляет?
А вы считали среднее число проб в этом вашем случае?
(Принимая, что коэффициент меньше 0,5 — это несколько маргинальная ситуация. Если кто-то и меняет коэфф., то в большую сторону от дефолтного, как мне кажется.)
index(){
check direct hit
if( hit ) -> return values[index];
else -> return probingStartingWith(....);
}
множество реализаций, скажем, метода index(), то JIT не сможет сделать эффективную девиртуализацию
А вы не знаете конкретнее о порогах размеров методов, которые учитываются ГИТом ХотСпота?Можно вывести -XX:+PrintFlagsFinal, и грепнуть там что-то вроде InlineThreshold. Но я думаю, на это не стоит закладываться.
MaxInlineSize maximum bytecode size of a method to be inlined 35
MaxBCEAEstimateSize Maximum bytecode size of a method to be analyzed by BC EA. 150
MaxInlineLevel maximum number of nested calls that are inlined 9
FreqInlineSize Maximum bytecode size of a frequently executed method to be inlined. 325
if( object.klass == impl1.klass )
call impl1.method
else if( object.klass == impl2.klass )
call impl2.method
else
call _vtable[index]
Вместо него считалось число итераций, и, когда оно переваливало за некий порог (скажем, capacity ^ 1/3), таблица увеличивалась.
Trove 4.0? Примитивы в стандартном каркасе коллекций из Java 8