3 миллиарда записей в Java Map на 16 GB RAM

от автора

Одним дождливым вечером я размышлял о памяти менеджмент в Java и как эффективно использовать Java коллекции. Я сделал простой эксперимент, сколько записей я могу вставить map с 16 Гб оперативной памяти?Целью этого эксперимента является исследование внутренних расходов памяти на управление коллекциями. Поэтому я решил использовать маленькие ключи и малые значения. Все тесты проводились на 64-битных Linux Kubuntu 12.04. JVM 64bit Oracle Java 1.7.0_09-b05 с HotSpot 23.5-b02. Включены сжатые указатели (-XX: + UseCompressedOops) по умолчанию на этой JVM.Первый тест с java.util.TreeMap. Вставляет число в map, работает пока память не заканчивается. JVM параметры для этого теста-Xmx15Gimport java.util.*; Map m = new TreeMap();for(long counter=0;;counter++){ m.put(counter,""); if(counter%1000000==0) System.out.println(""+counter);}Этот пример закончился 172 миллионами. Ближе к концу процесс замедлился благодаря агрессивной деятельности сборщика мусора. На втором заезде я заменил TreeMap на HashMap, он закончился 182 миллионами.По умолчанию Java коллекции не являются супер эффективными. Так давайте попробуем более оптимизированы по памяти: Я выбрал LongHashMap из MapDB, который использует примитивные длинные ключи и оптимизирован чтоб иметь небольшой объем памяти. JVM настройки снова -Xmx15Gimport org.mapdb.*LongMap m = new LongHashMap(); for(long counter=0;;counter++){ m.put(counter,""); if(counter%1000000==0) System.out.println(""+counter);}На этот раз счетчик остановился на 276 миллионов записей. Опять же ближе к концу процесс замедлился благодаря агрессивной деятельности сборщика мусора. Похоже, что это предел для динамических коллекций, сбор мусора приносит дополнительные расходы.Настало время, чтобы выкатить настоящее оружие:-). Мы всегда можем уйти от динамической памяти, где сборщик мусора не увидит наши данные. Позвольте мне представить вам MapDB, он предоставляет TreeMap и HashMap при поддержке базы данных. Поддерживает различные режимы хранения, включая вариант который не в динамической памяти. Так что давайте запустим предыдущий пример, но теперь Map без динамической памяти. Во-первых, это несколько строк, чтобы настроить и открыть базу данных, прямой доступ в память с выключенными транзакциями. Следующая строка создает новый Map в БД.import org.mapdb.*DB db = DBMaker .newDirectMemoryDB() .transactionDisable() .make();Map m = db.getTreeMap(«test»);for(long counter=0;;counter++){ m.put(counter,""); if(counter%1000000==0) System.out.println(""+counter);}Это Мap не находящийся в динамической памяти, так что мы нужны разные настройки JVM:-XX:MaxDirectMemorySize=15G -Xmx128M. Память закончилась на 980 миллионах.Но MapDB можно сделать лучше. Проблема в предыдущем примере это фрагментация, узел дерева (b-tree) изменяет свой размер на каждой вставке. Решение заключается в кашировании узлов дерева, прежде чем они вставлены. Это уменьшает фрагментацию при записи до минимума. поменяем конфигурацию DB:DB db = DBMaker .newDirectMemoryDB() .transactionDisable() .asyncFlushDelay(100) .make();Map m = db.getTreeMap(«test»); Память закончилась на 1,738 миллионов записей, через 31 минуту.MapDB можно сделать еще лучше — увеличив размер узла в дереве от 32 до 120 записей и включить прозрачное сжатие: DB db = DBMaker .newDirectMemoryDB() .transactionDisable() .asyncFlushDelay(100) .compressionEnable() .make(); Map m = db.createTreeMap(«test»,120, false, null, null, null);Этот пример заканчивает память на 3,315 миллионах записей. Это медленнее, благодаря сжатию, но он по-прежнему завершается в течение нескольких часов. Я мог бы, вероятно, сделать некоторые оптимизации (специальные сериализаторы) и увеличить количество записей, где-то около к 4 миллиардам.Может быть, бы спросите, как все эти записи могут поместиться там. Ответ delta-key компрессия. Также вставлением упорядоченных ключей в B-Tree является лучшим сценарием и MapDB немного оптимизирована для него. Наихудший сценарий вставляет ключи в случайном порядке.delta-key компрессия по умолчанию на всех примерах. В этом примере я активировал Zlib компрессию. DB db = DBMaker .newDirectMemoryDB() .transactionDisable() .asyncFlushDelay(100) .make(); Map m = db.getTreeMap(«test»); Random r = new Random(); for(long counter=0;;counter++){ m.put(r.nextLong(),""); if(counter%1000000==0) System.out.println(""+counter); }Но даже при случайном порядке MapDB сможет хранить 651 млн записей, почти в 4 раза больше, чем на основе обычных динамических коллекций.У этого маленького упражнения не так уж много целей. Это лишь один из способов оптимизировать MapDB. Наиболее удивительным является то, что скорость вставки была отличной и MapDB может конкурировать коллекциями в памяти.https://github.com/jankotek/jdbm3

ссылка на оригинал статьи http://habrahabr.ru/post/158451/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *