Оптимизация производительности Java HashMap / альтернатива - PullRequest
98 голосов
/ 18 ноября 2009

Я хочу создать большой HashMap, но производительность put() недостаточно хороша. Есть идеи?

Другие предложения по структуре данных приветствуются, но мне нужна функция поиска Java Map:

map.get(key)

В моем случае я хочу создать карту с 26 миллионами записей. При использовании стандартного Java HashMap скорость размещения становится невыносимо низкой после 2-3 миллионов вставок.

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

Мой метод хеш-кода:

byte[] a = new byte[2];
byte[] b = new byte[3];
...

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

Я использую ассоциативное свойство сложения, чтобы равные объекты имели одинаковый хэш-код. Массивы представляют собой байты со значениями в диапазоне от 0 до 51. Значения используются только один раз в любом массиве. Объекты равны, если массивы a содержат одинаковые значения (в любом порядке) и то же самое относится к массиву b. Таким образом, a = {0,1} b = {45,12,33} и a = {1,0} b = {33,45,12} равны.

РЕДАКТИРОВАТЬ, некоторые примечания:

  • Несколько человек критиковали использование хеш-карты или другой структуры данных для хранения 26 миллионов записей. Я не понимаю, почему это может показаться странным. Это выглядит как классическая проблема структур данных и алгоритмов для меня. У меня 26 миллионов элементов, и я хочу иметь возможность быстро вставлять их и искать их в структуре данных: предоставьте мне структуру данных и алгоритмы.

  • Установка начальной емкости Java HashMap по умолчанию на 26 миллионов снижает производительность.

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

Ответы [ 25 ]

0 голосов
/ 18 ноября 2009

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

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

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

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

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

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

EDIT

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

см. Из 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 методом в качестве «области возможностей»;)

EDIT

Не является ли HashMap

Согласно вашему последнему изданию.

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

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

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

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

Проблемы НЕ ХЭШМАП находится где-то еще в вашем коде.

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

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

note Я не использовал диапазон от 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 голосов
/ 08 февраля 2013

Используемые популярные методы хеширования не очень хороши для больших наборов, и, как указано выше, используемый хеш особенно плох. Лучше использовать алгоритм хеширования с высоким смешиванием и охватом, такой как BuzHash (пример реализации на http://www.java2s.com/Code/Java/Development-Class/AveryefficientjavahashalgorithmbasedontheBuzHashalgoritm.htm)

0 голосов
/ 06 июля 2010

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

0 голосов
/ 21 ноября 2009

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

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

0 голосов
/ 18 ноября 2009

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

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

...