HashMap хранится на диске очень медленно для чтения обратно с диска - PullRequest
0 голосов
/ 13 июля 2011

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

например:

  • 123.345.432 = 00001
  • 123.354.433 = 00002

Карта проверяется uid, чтобы убедиться, что будет использоваться тот же внутренний идентификатор. Если что-то повторно отправлено в приложение.

DICOMUID2StudyIdentiferMap определен следующим образом:

private static Map DICOMUID2StudyIdentiferMap = Collections.synchronizedMap(new HashMap());

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

Его считывают с диска, выполняя:

FileInputStream f = new FileInputStream( studyUIDFile );  
ObjectInputStream s = new ObjectInputStream( f );

Map loadedMap = ( Map )s.readObject();
DICOMUID2StudyIdentiferMap = Collections.synchronizedMap( loadedMap );

HashMap записывается на диск с помощью:

FileOutputStream f = new FileOutputStream( studyUIDFile );
ObjectOutputStream s = new ObjectOutputStream( f );

s.writeObject(DICOMUID2StudyIdentiferMap);

Проблема, с которой я сталкиваюсь, заключается в том, что локальная работа в Eclipse - это нормально, но когда приложение работает в обычном режиме на компьютере, HashMap загружается с диска в течение нескольких минут. После загрузки также требуется много времени, чтобы проверить предыдущее значение, например, посмотрев, вернет ли DICOMUID2StudyIdentiferMap.put (..., ...) значение.

Я загружаю один и тот же объект карты в обоих случаях, его файл ~ 400 КБ. Содержащаяся в нем HashMap содержит около 3000 пар ключ-значение.

Почему это так медленно на одной машине, но не в затмении?

Машина - это виртуальная машина под управлением XP, только недавно она начала медленно читать HashMap, поэтому она должна быть связана с ее размером, однако 400 КБ не очень большая, я не думаю.

Любой совет приветствуется, TIA

Ответы [ 6 ]

4 голосов
/ 13 июля 2011

Комментируя @biziclop, вы должны начать с использования профилировщика, чтобы увидеть, на что ваше приложение тратит все свое время.

Если это не даст вам никаких результатов, вот пара теорий.

  • Возможно, ваше приложение приближается к концу кучи.Поскольку JVM почти исчерпал кучу, он может потратить почти все свое время на сбор мусора в тщетной попытке продолжить работу.Это отобразится, если вы включите ведение журнала GC.

  • Возможно, что ObjectInputStream и ObjectOutputStream выполняют огромное количество небольших системных вызовов чтения.Попробуйте обернуть файловые потоки буферизованными потоками и посмотреть, заметно ли это ускоряется.

Почему это так медленно на одной машине, но не в затмении?

Теория "полной кучи" могла бы объяснить это.Размер кучи по умолчанию для Eclipse намного больше, чем для приложения, запущенного с использованием java ... без параметров размера кучи.

2 голосов
/ 13 июля 2011

Не уверен, что сериализация вашей карты - лучший вариант.Если карта основана на диске для сохранения, почему бы не использовать библиотеку, предназначенную для диска?Проверьте Киотский кабинет .Это на самом деле написано на C ++, но есть Java API.Я использовал его несколько раз, он очень прост в использовании, очень быстр и может масштабироваться до огромных размеров.

Это пример, который я копирую / вставляю для Токио кабинета,старая версия Киото, но в основном она такая же:

import tokyocabinet.HDB;

....

String dir = "/path/to/my/dir/";
HDB hash = new HDB();

// open the hash for read/write, create if does not exist on disk
if (!hash.open(dir + "unigrams.tch", HDB.OWRITER | HDB.OCREAT)) {
    throw new IOException("Unable to open " + dir + "unigrams.tch: " + hash.errmsg());
}

// Add something to the hash
hash.put("blah", "my string");

// Close it
hash.close();
1 голос
/ 13 июля 2011

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

1 голос
/ 13 июля 2011

Вот список из 122 баз данных NoSQL, которые вы можете использовать в качестве альтернативы.

У вас есть две дорогостоящие операции: одна - сериализация объектов, а вторая - доступ к диску. Вы можете ускорить доступ, только читая / записывая нужные данные. Seralization вы можете ускорить с помощью пользовательского формата.

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


private Map<Integer, Integer> mapping = new LinkedHashMap<Integer, Integer>();

public void saveTo(File file) throws IOException {
    DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
    dos.writeInt(mapping.size());
    for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
        dos.writeInt(entry.getKey());
        dos.writeInt(entry.getValue());
    }
    dos.close();
}

public void loadFrom(File file) throws IOException {
    DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
    mapping.clear();
    int len = dis.readInt();
    for (int i = 0; i < len; i++)
        mapping.put(dis.readInt(), dis.readInt());
    dis.close();
}

public static void main(String[] args) throws IOException {
    Random rand = new Random();
    Main main = new Main();
    for (int i = 1; i <= 3000; i++) {
        // 100,000,000 to 999,999,999
        int uid = 100000000 + rand.nextInt(900000000); 
        main.mapping.put(uid, i);
    }
    final File file = File.createTempFile("deleteme", "data");
    file.deleteOnExit();
    for (int i = 0; i < 10; i++) {
        long start = System.nanoTime();
        main.saveTo(file);
        long mid = System.nanoTime();
        new Main().loadFrom(file);
        long end = System.nanoTime();
        System.out.printf("Took %.3f ms to save and %.3f ms to load %,d entries.%n",
                (end - mid) / 1e6, (mid - start) / 1e6, main.mapping.size());
    }
}

печать

Took 1.203 ms to save and 1.706 ms to load 3,000 entries.
Took 1.209 ms to save and 1.203 ms to load 3,000 entries.
Took 0.961 ms to save and 0.966 ms to load 3,000 entries.

Использование TIntIntHashMap вместо этого примерно на 10% быстрее.

Увеличение размера карты до 1 миллиона записей.

Took 412.718 ms to save and 62.009 ms to load 1,000,000 entries.
Took 403.135 ms to save and 61.756 ms to load 1,000,000 entries.
Took 399.431 ms to save and 61.816 ms to load 1,000,000 entries.
1 голос
/ 13 июля 2011

Voldemort - популярное хранилище ключей с открытым исходным кодом от Linkedin. Я советую вам взглянуть на исходный код, чтобы увидеть, как они работают. Прямо сейчас я смотрю на часть сериализации на https://github.com/voldemort/voldemort/blob/master/src/java/voldemort/serialization/ObjectSerializer.java. Смотря на код, который они используют ByteArrayOutputStream , который, как я полагаю, является более эффективным способом чтения / записи на / с диска.

Почему это так медленно на одной машине, но не в затмении?

Не совсем понятно из вашего вопроса, но работает ли Eclipse в ВМ (VirtualBox?)? Потому что в таком случае это может быть быстрее, потому что вся виртуальная машина хранится в памяти, что намного быстрее, чем доступ к диску.

1 голос
/ 13 июля 2011

Может быть, вам следует искать альтернативы, которые работают аналогично Map, например SimpleDB, BerkeleyDB или Google BigTable.

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