Оптимизация Java HashMap производительности / альтернатива

Если Вы всегда хотите это поведение, Вы могли бы поместить ПОСЛЕ ТОГО, КАК ВСТАВЛЯЮТ, включают TableA, который обновит таблицу B.

98
задан 10 revs, 2 users 100% 18 December 2009 в 20:38
поделиться

22 ответа

Как многие отмечали, Виноват метод hashCode () . Он генерировал всего около 20 000 кодов для 26 миллионов различных объектов. Это в среднем 1300 объектов на хеш-ведро = очень-очень плохо. Однако, если я превращу два массива в число в базе 52, я гарантированно получу уникальный хэш-код для каждого объекта:

public int hashCode() {       
    // assume that both a and b are sorted       
    return a[0] + powerOf52(a[1], 1) + powerOf52(b[0], 2) + powerOf52(b[1], 3) + powerOf52(b[2], 4);
}

public static int powerOf52(byte b, int power) {
    int result = b;
    for (int i = 0; i < power; i++) {
        result *= 52;
    }
    return result;
}

Массивы сортируются, чтобы гарантировать, что эти методы соответствуют контракту hashCode () , согласно которому одинаковые объекты имеют одинаковый хэш-код. При использовании старого метода среднее количество операций ввода-вывода в секунду по блокам из 100 000 операций ввода-вывода, от 100 000 до 2 000 000 было:

168350.17
109409.195
81344.91
64319.023
53780.79
45931.258
39680.29
34972.676
31354.514
28343.062
25562.371
23850.695
22299.22
20998.006
19797.799
18702.951
17702.434
16832.182
16084.52
15353.083

Использование нового метода дает:

337837.84
337268.12
337078.66
336983.97
313873.2
317460.3
317748.5
320000.0
309704.06
310752.03
312944.5
265780.75
275540.5
264350.44
273522.97
270910.94
279008.7
276285.5
283455.16
289603.25

Намного лучше. Старый метод сработал очень быстро, а новый сохранил хорошую пропускную способность.

54
ответ дан 24 November 2019 в 05:17
поделиться

Вначале разместить большую карту. Если вы знаете, что в нем будет 26 миллионов записей и у вас есть для него память, сделайте новый HashMap (30000000) .

Вы уверены, что у вас достаточно памяти для 26 миллионов записей с 26 миллионами ключей и ценности? Для меня это звучит как много воспоминаний. Вы уверены, что сборка мусора все еще работает на вашей отметке в 2–3 миллиона? Я могу представить это как узкое место.

0
ответ дан 24 November 2019 в 05:17
поделиться

Вы можете попытаться кэшировать вычисленный хэш-код в ключевом объект.

Примерно так:

public int hashCode() {
  if(this.hashCode == null) {
     this.hashCode = computeHashCode();
  }
  return this.hashCode;
}

private int computeHashCode() {
   int hash = 503;
   hash = hash * 5381 + (a[0] + a[1]);
   hash = hash * 5381 + (b[0] + b[1] + b[2]);
   return hash;
}

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

Редактировать: Кажется, что кеширование с кодовыми значениями не имеет смысла когда вы добавляете каждый ключ на карту только один раз. В другой ситуации это может быть полезно.

1
ответ дан 24 November 2019 в 05:17
поделиться

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

Затем я предложит использовать профилировщик, чтобы увидеть, что на самом деле происходит и на что уходит время выполнения. Например, выполняется ли метод hashCode () миллиарды раз?

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

Другой вариант - это какой-нибудь механизм базы данных, который легче, чем полная СУБД SQL. Что-то вроде Berkeley DB , возможно.

Примечание.

1
ответ дан 24 November 2019 в 05:17
поделиться

SQLite позволяет использовать его в памяти.

1
ответ дан 24 November 2019 в 05:17
поделиться

Рассматривали ли вы возможность использования встроенной базы данных для этого? Посмотрите Berkeley DB . Это открытый исходный код, сейчас принадлежит Oracle.

Он хранит все в виде пары «ключ-> значение», это НЕ СУБД. и он стремится быть быстрым.

1
ответ дан 24 November 2019 в 05:17
поделиться

Вы можете попробовать использовать базу данных в памяти, например HSQLDB .

2
ответ дан 24 November 2019 в 05:17
поделиться

Я заметил одну вещь в вашем методе hashCode () : порядок элементов в массивах a [] и b [] не имеет значения. Таким образом, (a [] = {1,2,3}, b [] = {99,100}) будет хешировать до того же значения, что и (a [] = {3,1,2} , b [] = {100,99}) . Фактически все ключи k1 и k2 , где сумма (k1.a) == сумма (k2.a) и сумма (k1.b) = sum (k2.b) приведет к столкновениям. Я предлагаю присвоить вес каждой позиции массива:

hash = hash * 5381 + (c0*a[0] + c1*a[1]);
hash = hash * 5381 + (c0*b[0] + c1*b[1] + c3*b[2]);

где, c0 , c1 и c3 - различные константы ( при необходимости вы можете использовать различные константы для b ). Это должно немного выровнять ситуацию.

17
ответ дан 24 November 2019 в 05:17
поделиться

Моя первая идея - убедиться, что вы правильно инициализируете свой HashMap. Из JavaDocs для HashMap :

Экземпляр HashMap имеет два параметра, которые влияют на его производительность: начальная емкость и коэффициент загрузки. Емкость - это количество сегментов в хэш-таблице, а начальная емкость - это просто емкость на момент создания хеш-таблицы. Коэффициент загрузки - это мера того, насколько может быть заполнена хеш-таблица до того, как ее емкость автоматически увеличится. Когда количество записей в хеш-таблице превышает произведение коэффициента загрузки и текущей емкости, хеш-таблица перестраивается (то есть перестраиваются внутренние структуры данных), так что хеш-таблица имеет примерно в два раза больше сегментов.

Итак, если вы начинаете со слишком маленьким HashMap,

7
ответ дан 24 November 2019 в 05:17
поделиться

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

Если у вас всегда есть значения в диапазоне 0 ..51 для представления каждого из этих значений потребуется 6 бит. Если у вас всегда есть 5 значений, вы можете создать 30-битный хэш-код со сдвигом влево и дополнениями:

    int code = a[0];
    code = (code << 6) + a[1];
    code = (code << 6) + b[0];
    code = (code << 6) + b[1];
    code = (code << 6) + b[2];
    return code;

Сдвиг влево выполняется быстро, но оставляет вам хэш-коды, которые не распределены равномерно (поскольку 6 битов подразумевают диапазон 0..63). Альтернативный вариант - умножить хэш на 51 и сложить каждое значение. Это все равно не будет идеально распределено (например, {2,0} и {1,52} будут конфликтовать), и будет медленнее, чем сдвиг.

    int code = a[0];
    code *= 51 + a[1];
    code *= 51 + b[0];
    code *= 51 + b[1];
    code *= 51 + b[2];
    return code;
1
ответ дан 24 November 2019 в 05:17
поделиться

Чтобы подробнее рассказать о Паскале: Вы понимаете, как работает HashMap? У вас есть некоторое количество слотов в вашей хеш-таблице. Хеш-значение для каждого ключа находится и затем сопоставляется с записью в таблице. Если два значения хэша сопоставляются с одной и той же записью - «коллизия хешей» - HashMap строит связанный список.

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

Итак, если вы видите проблемы с производительностью, первое, что я проверю, это: получаю ли я случайное распределение хэш-кодов? Если нет, вам нужна лучшая хеш-функция. Что ж, «лучше» в этом случае может означать «лучше для моего конкретного набора данных». Например, предположим, что вы работали со строками и взяли длину строки в качестве хеш-значения. (Не то, как работает Java String.hashCode, но я просто привожу простой пример.) Если ваши строки имеют очень разную длину, от 1 до 10 000, и довольно равномерно распределены по этому диапазону, это может быть очень хорошей хеш-функцией. Но если все ваши строки состоят из 1 или 2 символов, это будет очень плохая хеш-функция.

Изменить: я должен добавить: каждый раз, когда вы добавляете новую запись, HashMap проверяет, не является ли это дубликатом. Когда возникает конфликт хешей, он должен сравнивать входящий ключ с каждым ключом, сопоставленным с этим слотом. Таким образом, в худшем случае, когда все хешируется в один слот, второй ключ сравнивается с первым ключом, третий ключ сравнивается с # 1 и # 2, четвертый ключ сравнивается с # 1, # 2 и # 3. и т. д. К тому времени, когда вы дойдете до ключевого №1 миллиона, вы сделали более триллиона сравнений.

@Oscar: Ммм, я не понимаю, как это " HashMap создает связанный список. Затем, поскольку он должен проверять каждый новый ключ, чтобы увидеть, действительно ли он является дубликатом существующего ключа, каждая попытка добавить новую запись, которая сопоставляется с тем же слотом, должна преследовать связанный список, проверяя каждую существующую запись, чтобы убедиться, что это является дубликатом ранее замеченного ключа, или если это новый ключ.

Обновление спустя много времени после исходного сообщения

Я только что проголосовал за этот ответ через 6 лет после публикации, что заставило меня повторно прочтите вопрос.

Хеш-функция, указанная в вопросе, не является хорошим хешем для 26 миллионов записей.

Она складывает вместе a [0] + a [1] и b [0] + b [1] + Би 2]. Он говорит, что значения каждого байта находятся в диапазоне от 0 до 51, что дает только (51 * 2 + 1) * (51 * 3 + 1) = 15 862 возможных значения хеш-функции. При 26 миллионах записей это означает в среднем около 1639 записей на одно значение хеш-функции. Это много-много столкновений, требуется много-много последовательных поисков через связанные списки.

OP говорит, что разные порядки в массиве a и массиве b следует считать равными, то есть [[1,2], [3,4,5]]. equals ( [[2,1], [5,3,4]]), поэтому для выполнения контракта они должны иметь одинаковые хэш-коды. Хорошо. Тем не менее, существует более 15 000 возможных значений. Его вторая предложенная хеш-функция намного лучше, дает более широкий диапазон.

Хотя, как заметил кто-то другой, для хэш-функции кажется неуместным изменять другие данные. Было бы разумнее «нормализовать» объект при его создании или заставить хеш-функцию работать с копиями массивов. Кроме того, использование цикла для вычисления констант каждый раз через функцию неэффективно. Поскольку здесь всего четыре значения, Я бы написал либо

return a[0]+a[1]*52+b[0]*52*52+b[1]*52*52*52+b[2]*52*52*52*52;

, что заставит компилятор выполнить вычисление один раз во время компиляции; или иметь 4 статические константы, определенные в классе.

Кроме того, первый черновик хеш-функции содержит несколько вычислений, которые ничего не делают для добавления к диапазону выходных данных. Обратите внимание, что он сначала устанавливает hash = 503, а затем умножает на 5381, прежде чем даже рассматривать значения из класса. Итак ... фактически он добавляет 503 * 5381 к каждому значению. Что это дает? Добавление константы к каждому значению хеш-функции просто сжигает циклы процессора, не выполняя ничего полезного. Урок здесь: усложнение хэш-функции - не цель. Цель состоит в том, чтобы получить широкий диапазон различных значений, а не просто добавить сложности ради сложности.

первый черновик хэш-функции содержит несколько вычислений, которые ничего не делают для увеличения диапазона выходных данных. Обратите внимание, что он сначала устанавливает hash = 503, а затем умножает на 5381, прежде чем даже рассматривать значения из класса. Итак ... фактически он добавляет 503 * 5381 к каждому значению. Что это дает? Добавление константы к каждому значению хеш-функции просто сжигает циклы процессора, не выполняя ничего полезного. Урок здесь: усложнение хеш-функции - не цель. Цель состоит в том, чтобы получить широкий диапазон различных значений, а не просто добавить сложности ради сложности.

первый черновик хэш-функции содержит несколько вычислений, которые ничего не делают для увеличения диапазона выходных данных. Обратите внимание, что он сначала устанавливает hash = 503, а затем умножает его на 5381, прежде чем даже рассматривать значения из класса. Итак ... фактически он добавляет 503 * 5381 к каждому значению. Что это дает? Добавление константы к каждому значению хеш-функции просто сжигает циклы процессора, не выполняя ничего полезного. Урок здесь: усложнение хеш-функции - не цель. Цель состоит в том, чтобы получить широкий диапазон различных значений, а не просто добавить сложности ради сложности.

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

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

16
ответ дан 24 November 2019 в 05:17
поделиться

HashMap имеет начальную емкость, и производительность HashMap очень сильно зависит от hashCode, который создает базовые объекты.

Попробуйте настроить оба.

4
ответ дан 24 November 2019 в 05:17
поделиться

Если ключи имеют какой-либо шаблон, вы можете разделить карту на более мелкие карты и получить карту индекса.

Пример: Ключи: 1,2,3, .... n 28 карт по 1 миллиону каждая. Индексная карта: 1-1,000,000 -> Карта1 1,000,000-2,000,000 -> Map2

Итак, вы будете выполнять два поиска, но набор ключей будет 1,000,000 против 28,000,000. Вы также можете легко сделать это с помощью шаблонов укусов.

Если ключи полностью случайны, это не сработает

4
ответ дан 24 November 2019 в 05:17
поделиться

Если два байтовых массива, которые вы упомянули, представляют собой весь ваш ключ, значения находятся в диапазоне от 0 до 51, уникальны, а порядок в массивах a и b не имеет значения, моя математика говорит мне, что существует всего около 26 миллионов возможных перестановок, и вы, вероятно, пытаетесь заполнить карту значениями для всех возможных ключей.

В этом случае как заполнение, так и получение значений из вашего хранилища данных, конечно, будет намного быстрее, если вы используйте массив вместо HashMap и проиндексируйте его от 0 до 25989599.

4
ответ дан 24 November 2019 в 05:17
поделиться

Попадание в серую область «вкл / выкл тему», но это необходимо для устранения путаницы относительно предположения Оскара Рейеса о том, что больше коллизий хэша - это хорошо, потому что это уменьшает количество элементов в HashMap . Я могу неправильно понять то, что говорит Оскар, но, похоже, я не единственный: kdgregory, delfuego, Nash0, и я, похоже, разделяем одно (неправильное) понимание.

Если я понимаю, о чем говорит Оскар того же класса с тем же хэш-кодом, он предлагает, чтобы только один экземпляр класса с заданным хэш-кодом был вставлен в HashMap. Например, если у меня есть экземпляр SomeClass с хэш-кодом 1 и второй экземпляр SomeClass с хэш-кодом 1, вставляется только один экземпляр SomeClass.

Пример Java pastebin по адресу http: // pastebin . com / f20af40b9 , похоже, указывает на то, что вышесказанное правильно резюмирует то, что предлагает Оскар.

Независимо от понимания или недопонимания, происходит следующее: разные экземпляры одного и того же класса не вставляются только один раз в HashMap, если у них одинаковый хэш-код - пока не будет определено, равны ли ключи или не. Контракт хэш-кода требует, чтобы одинаковые объекты имели одинаковый хэш-код; однако не требуется, чтобы у неравных объектов были разные хэш-коды (хотя это может быть желательно по другим причинам) [1].

Пример pastebin.com/f20af40b9 (на который Оскар ссылается по крайней мере дважды) следует, но измененный немного использовать утверждения JUnit, а не строки печати. Этот пример используется для поддержки предложения о том, что одни и те же хэш-коды вызывают коллизии, и когда классы одинаковы, создается только одна запись (например, только одна строка в этом конкретном случае):

@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
    String s = new String("ese");
    String ese = new String("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // AND equal
    assertTrue(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(2, map.size());

    assertEquals(2, map.get("ese"));
    assertEquals(3, map.get(some));

    assertTrue(s.equals(ese) && s.equals("ese"));
}

class SomeClass {
    public int hashCode() {
        return 100727;
    }
}

Однако хэш-код не является полным сказка. Пример pastebin игнорирует тот факт, что оба s и ese равны: они обе являются строкой «ese». Таким образом, вставка или получение содержимого карты с использованием s или ese или «ese» в качестве ключа эквивалентны, поскольку s.equals ( ese) && s.equals ("ese") .

Второй тест демонстрирует ошибочный вывод о том, что одинаковые хэш-коды одного и того же класса являются причиной, по которой ключ -> значение s -> 1 заменяется на ese -> 2 , когда map.put (ese, 2) вызывается в первом тесте. Во втором тесте s и ese по-прежнему имеют тот же хэш-код (что подтверждается assertEquals (s.hashCode (), ese.hashCode ()); ) И они одного класса. Однако s и ese являются экземплярами MyString в этом тесте, а не экземплярами Java String - с единственной разницей, относящейся к этому тесту: равенство: String s равно String ese в первом тесте выше, тогда как MyStrings s не равно MyString ese во втором тесте:

@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
    MyString s = new MyString("ese");
    MyString ese = new MyString("ese");
    // same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    // same class
    assertEquals(s.getClass(), ese.getClass());
    // BUT not equal
    assertFalse(s.equals(ese));

    Map map = new HashMap();
    map.put(s, 1);
    map.put(ese, 2);
    SomeClass some = new SomeClass();
    // still  same hash right?
    assertEquals(s.hashCode(), ese.hashCode());
    assertEquals(s.hashCode(), some.hashCode());

    map.put(some, 3);
    // what would we get?
    assertEquals(3, map.size());

    assertEquals(1, map.get(s));
    assertEquals(2, map.get(ese));
    assertEquals(3, map.get(some));
}

/**
 * NOTE: equals is not overridden so the default implementation is used
 * which means objects are only equal if they're the same instance, whereas
 * the actual Java String class compares the value of its contents.
 */
class MyString {
    String i;

    MyString(String i) {
        this.i = i;
    }

    @Override
    public int hashCode() {
        return 100727;
    }
}

Основываясь на более позднем комментарии, Оскар, кажется, перевернул то, что он сказал ранее, и признает важность равных. Тем не менее, по-прежнему кажется, что значение имеет равенство, а не «один и тот же класс», в этом вопросе используется один и тот же класс (подождите, тот же класс используется правильно?). Это означает, что при использовании одного и того же хэша используется одна и та же запись, а «список» записей отсутствует. - Оскар Рейес "

или

" На самом деле это повысило бы производительность. Чем больше коллизий, тем меньше записей в хэш-таблице. меньше работы. Я уверен, что это не хеш (который выглядит нормально) или хеш-таблица (которая отлично работает), это при создании объекта, где производительность ухудшается. - Оскар Рейес "

или

" @ kdgregory: Да, но только если конфликт происходит с разными классами, для одного и того же класса (что и есть) используется одна и та же запись. - Оскар Рейес »

Опять же, я могу неправильно понять, что Оскар на самом деле пытался сказать. Однако такое же целое число, при условии, что информация не используется в равных сравнениях по объект изменен. Это целое число не обязательно должно оставаться согласованным от одного выполнения приложения к другому выполнению того же самого приложения.

  • Если два объекта равны в соответствии с методом равенства s (Obj ect), то вызов метода hashCode для каждого из двух объектов должен производить то же самое целочисленный результат.

  • Не требуется, чтобы, если два объекта не равны в соответствии с методом равенства s (Object), то вызов метода hashCode для каждого из двух объектов должны давать отличные целочисленные результаты. Однако программист должен быть осознавать, что получение различных целочисленных результатов для неравных объектов может улучшить производительность хэш-таблиц.

  • 7
    ответ дан 24 November 2019 в 05:17
    поделиться

    Я бы предложил трехкомпонентный подход:

    1. Запустите Java с большим объемом памяти: java -Xmx256M , например, для работы с 256 мегабайтами. Используйте больше, если необходимо, и у вас много оперативной памяти.

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

    3. Используйте лучший алгоритм хеширования. Тот, который вы разместили, вернет тот же хеш, где a = {0, 1}, как и где a = {1, 0}, при прочих равных.

    Используйте то, что Java дает вам бесплатно.

    public int hashCode() {
        return 31 * Arrays.hashCode(a) + Arrays.hashCode(b);
    }
    

    I '

    7
    ответ дан 24 November 2019 в 05:17
    поделиться

    Я здесь опоздал, но пара комментариев о больших картах:

    1. Как подробно обсуждалось в других сообщениях, с хорошим hashCode () 26 миллионов записей на карте - это не так уж и много сделки.
    2. Однако потенциально скрытая проблема здесь - это влияние гигантских карт на сборщик мусора.

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

    Каждая запись в Java HashMap требует трех объектов: ключа, значения и записи, которая связывает их вместе. Таким образом, 26M записей на карте означает 26M * 3 == 78M объектов. Это нормально, пока вы не достигнете полного GC. Тогда у вас есть проблема паузы в мире. Сборщик мусора проверит каждый из 78 миллионов объектов и определит, что все они живы. 78M + объектов - это просто множество объектов, на которые стоит смотреть. Если ваше приложение может выдерживать периодические длительные (возможно, несколько секунд) паузы, проблем нет. Если вы пытаетесь добиться каких-либо гарантий задержки, у вас может быть серьезная проблема (конечно, если вам нужны гарантии задержки, Java - не та платформа, которую следует выбирать :)) Если значения на ваших картах быстро меняются, вы можете в конечном итоге часто получать полные сборы что сильно усугубляет проблему.

    Я не знаю отличного решения этой проблемы. Идеи:

    • Иногда можно настроить сборщик мусора и размеры кучи так, чтобы «в основном» предотвращать полные сборщики мусора.
    • Если содержимое вашей карты сильно меняется, вы можете попробовать Javolution's FastMap - он может объединять объекты Entry , что может снизить частоту полных сборов
    • . Вы можете создать свою собственную карту impl и явно управлять памятью для byte [] (т.е. е. замените процессор на более предсказуемую задержку, сериализуя миллионы объектов в один байт [] - тьфу!)
    • Не используйте Java для этой части - поговорите с какой-то предсказуемой БД в памяти через сокет
    • Надеюсь, что новый сборщик G1 поможет (в основном относится к случаю высокого оттока)

    Просто некоторые мысли от человека, который провел много времени с гигантскими картами на Java.


    4
    ответ дан 24 November 2019 в 05:17
    поделиться

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

    a [0] + a [1] всегда будет между 0 и 512 . добавление b всегда приводит к числу от 0 до 768. умножьте их, и вы получите верхний предел в 400 000 уникальных комбинаций, если ваши данные идеально распределены между всеми возможными значениями каждого байта. Если ваши данные вообще регулярны, у вас, вероятно, будет гораздо меньше уникальных результатов этого метода.

    5
    ответ дан 24 November 2019 в 05:17
    поделиться

    Возможно, попробуйте использовать, если вам нужно синхронизировать

    http://commons.apache.org/collections/api/org/apache/commons/collections/FastHashMap.html

    0
    ответ дан 24 November 2019 в 05:17
    поделиться

    Вы можете попробовать две вещи:

    • Сделайте так, чтобы ваш метод hashCode возвращал что-то более простое и эффективное, например, последовательный int

    • Инициализируйте вашу карту как:

       Map map = new HashMap (30000000, .95f);
      

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

    Если это не сработает, рассмотрите возможность использования другого хранилища, такого как СУБД.

    ИЗМЕНИТЬ

    Странно, что установка начальной емкости снижает производительность в вашем случае.

    См. Из javadocs :

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

    Я сделал микропляж (который ни в коем случае не является окончательным, но, по крайней мере, доказывает этот момент)

    $cat Huge*java
    import java.util.*;
    public class Huge {
        public static void main( String [] args ) {
            Map map = new HashMap( 30000000 , 0.95f );
            for( int i = 0 ; i < 26000000 ; i ++ ) { 
                map.put( i, i );
            }
        }
    }
    import java.util.*;
    public class Huge2 {
        public static void main( String [] args ) {
            Map map = new HashMap();
            for( int i = 0 ; i < 26000000 ; i ++ ) { 
                map.put( i, i );
            }
        }
    }
    $time java -Xms2g -Xmx2g Huge
    
    real    0m16.207s
    user    0m14.761s
    sys 0m1.377s
    $time java -Xms2g -Xmx2g Huge2
    
    real    0m21.781s
    user    0m20.045s
    sys 0m1.656s
    $
    

    Таким образом, использование начальной емкости снижается с 21 до 16 из-за перефазировки. Это оставляет нам ваш метод hashCode как «область возможностей»;)

    РЕДАКТИРОВАТЬ

    Это не HashMap

    В соответствии с вашим последним изданием.

    Я думаю, вам действительно следует профилировать свое приложение и посмотреть, где используется память / процессор.

    Я создал класс, реализующий тот же hashCode

    Этот хэш-код дает миллионы коллизий, затем количество записей в HashMap резко сокращается.

    Я перехожу с 21 до 16 в моем предыдущем тесте на 10 и 8. Причина в том, что хэш-код вызывает большое количество коллизий, и вы сохраняете не 26 миллионов объектов, как вы думаете, а гораздо меньшее количество (я бы сказал, около 20 тысяч) Итак:

    Проблемы НЕ ХЭШ-КАРТА где-то еще в вашем коде.

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

    Вот моя реализация вашего класса.

    примечание Я не использовал диапазон 0-51, как вы, но от -126 до 127 для моих значений и допускает повторение, потому что я провел этот тест до того, как вы обновили ваш вопрос

    Единственная разница в том, что что в вашем классе будет больше коллизий, следовательно, на карте будет храниться меньше элементов.

    import java.util.*;
    public class Item {
    
        private static byte w = Byte.MIN_VALUE;
        private static byte x = Byte.MIN_VALUE;
        private static byte y = Byte.MIN_VALUE;
        private static byte z = Byte.MIN_VALUE;
    
        // Just to avoid typing :) 
        private static final byte M = Byte.MAX_VALUE;
        private static final byte m = Byte.MIN_VALUE;
    
    
        private byte [] a = new byte[2];
        private byte [] b = new byte[3];
    
        public Item () {
            // make a different value for the bytes
            increment();
            a[0] = z;        a[1] = y;    
            b[0] = x;        b[1] = w;   b[2] = z;
        }
    
        private static void increment() {
            z++;
            if( z == M ) {
                z = m;
                y++;
            }
            if( y == M ) {
                y = m;
                x++;
            }
            if( x == M ) {
                x = m;
                w++;
            }
        }
        public String toString() {
            return "" + this.hashCode();
        }
    
    
    
        public int hashCode() {
            int hash = 503;
            hash = hash * 5381 + (a[0] + a[1]);
            hash = hash * 5381 + (b[0] + b[1] + b[2]);
            return hash;
        }
        // I don't realy care about this right now. 
        public boolean equals( Object other ) {
            return this.hashCode() == other.hashCode();
        }
    
        // print how many collisions do we have in 26M items.
        public static void main( String [] args ) {
            Set set = new HashSet();
            int collisions = 0;
            for ( int i = 0 ; i < 26000000 ; i++ ) {
                if( ! set.add( new Item() ) ) {
                    collisions++;
                }
            }
            System.out.println( collisions );
        }
    }
    

    Использование этого класса имеет ключ для предыдущей программы

     map.put( new Item() , i );
    

    дает мне:

    real     0m11.188s
    user     0m10.784s
    sys 0m0.261s
    
    
    real     0m9.348s
    user     0m9.071s
    sys  0m0.161s
    
    0
    ответ дан 24 November 2019 в 05:17
    поделиться

    Как уже отмечалось, ваша реализация хэш-кода имеет слишком много конфликтов, и их исправление должно привести к достойной производительности. Более того, кеширование хэш-кодов и эффективное использование равно поможет.

    Если вам нужно еще больше оптимизировать:

    По вашему описанию есть только (52 * 51/2) * (52 * 51 * 50/6) = 29304600 различных ключей (из них 26000000, т.е. около 90%, будут присутствовать). Следовательно, вы можете разработать хэш-функцию без каких-либо коллизий и использовать простой массив, а не хэш-карту для хранения ваших данных, уменьшая потребление памяти и увеличивая скорость поиска:

    T[] array = new T[Key.maxHashCode];
    
    void put(Key k, T value) {
        array[k.hashCode()] = value;
    
    T get(Key k) {
        return array[k.hashCode()];
    }
    

    (Как правило, невозможно разработать эффективный, конфликтный- бесплатная хеш-функция, которая хорошо группируется, поэтому HashMap будет допускать коллизии, которые влекут за собой некоторые накладные расходы)

    Предполагая, что a и b отсортированы, вы можете использовать следующую хеш-функцию:

    public int hashCode() {
        assert a[0] < a[1]; 
        int ahash = a[1] * a[1] / 2 
                  + a[0];
    
        assert b[0] < b[1] && b[1] < b[2];
    
        int bhash = b[2] * b[2] * b[2] / 6
                  + b[1] * b[1] / 2
                  + b[0];
        return bhash * 52 * 52 / 2 + ahash;
    }
    
    static final int maxHashCode = 52 * 52 / 2 * 52 * 52 * 52 / 6;  
    

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

    1
    ответ дан 24 November 2019 в 05:17
    поделиться

    Некоторое время назад я провел небольшой тест со списком и хэшмапом. Забавно, но итерация по списку и поиск объекта заняли столько же времени в миллисекундах, сколько и использование функции hashmaps get... просто к сведению. Да, память - большая проблема при работе с хэшмапами такого размера.

    0
    ответ дан 24 November 2019 в 05:17
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: