Как я могу избежать конфликта string.intern () и сохранить объем памяти? - PullRequest
4 голосов
/ 28 июля 2011

Я анализирую довольно большой (200 МБ) XML-файл, в результате которого создается дерево объектов, каждый из которых определяет набор параметров (ключ = значение).Эта структура данных работает в веб-приложении Tomcat и используется для поиска этих параметров.

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

Сегодня я снова посещаю сервер, потому что людижаловаться на время запуска.Я выполняю профилирование на сервере и вижу, что синтаксический анализ XML с XPP3 занимает 40 секунд, а String.intern () - более 30 секунд.

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

Кто-нибудь подсчитывал цифры, чтобы посмотреть, стоит ли отбрасывать String.intern в пользу другого решения?

Так что вопрос?Как я могу получить как можно меньше конфликтов для таких проблем?

Спасибо, Стефан

Ответы [ 4 ]

3 голосов
/ 28 июля 2011

Добавьте дополнительный шаг косвенности: создайте вторую HashMap, в которой хранятся ключи, и сначала найдите ключи там, прежде чем вставлять их в структуры в памяти.Это даст вам гораздо больше гибкости, чем String # intern ().

Однако, если вам нужно анализировать этот XML-файл размером 200 МБ при каждом запуске tomcat, и дополнительные 10 секунд заставляют людей ворчать (они перезапускают tomcat каждыетак часто?) - это заставляет всплывающие флаги (вы рассматривали возможность использования базы данных, даже Apache Derby, для сохранения проанализированных данных?).

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

Похоже, что String.intern () не очень хорошо масштабируется, когда вы добавляете больше строк. Похоже, O (n) с количеством строк в пуле.

Random rand = new Random();
for(int i=0;i<100;i++) {
    long start = System.nanoTime();
    for(int j=0;j<100000;j++)
        Long.toString(rand.nextLong()).toString().intern();
    long time = System.nanoTime() - start;
    System.out.printf("Took %,d ns on average to intern() a random string%n", time/100000);
}

печать

Took 1,586 ns on average to intern() a random string
Took 3,843 ns on average to intern() a random string
Took 7,551 ns on average to intern() a random string
Took 13,436 ns on average to intern() a random string
Took 20,226 ns on average to intern() a random string
Took 27,609 ns on average to intern() a random string
Took 35,098 ns on average to intern() a random string
Took 42,439 ns on average to intern() a random string
Took 50,801 ns on average to intern() a random string
Took 20,975 ns on average to intern() a random string
Took 4,634 ns on average to intern() a random string
Took 10,512 ns on average to intern() a random string
Took 16,914 ns on average to intern() a random string
Took 23,601 ns on average to intern() a random string
Took 30,230 ns on average to intern() a random string
Took 36,184 ns on average to intern() a random string
Took 43,266 ns on average to intern() a random string

Вместо этого я использую массив в качестве пула строк.

private static void testHashArray(String[] strings2, int size) {
    String[] pool = new String[size];
    int hit=0, miss=0;
    long start2 = System.nanoTime();
    for (String s : strings2) {
        int hash = (s.hashCode() & 0x7fffffff) % pool.length;
        String s2 = pool[hash];
        if (s.equals(s2)) {
            hit++;
        } else {
            miss++;
        }
        if (s2 != s)
            pool[hash] = s;
    }
    long time2 = System.nanoTime() - start2;
    System.out.printf("Hash size: %,d took %.3f second. Hit/miss %,d/%,d %n", size, time2 / 1e9, hit, miss);
}

public static void main(String... args) {
    Random rand = new Random();

    // a million unique strings.
    String[] strings = new String[1000 * 1000];
    for (int i = 0; i < strings.length; i++)
        strings[i] = String.valueOf(rand.nextLong());
    // random selection of Strings
    String[] strings2 = new String[10 * 1000 * 1000];
    int totalSize = 0;
    for (int i = 0; i < strings2.length; i++) {
        int idx = (int) Math.pow(strings.length, rand.nextFloat());
        String s = strings[idx];
        strings2[i] = s;
        totalSize += s.length() + 16; // with overhead
    }
    System.out.printf("Original size %,d%n", totalSize);

    Set<String> uniqueStrings = Collections.newSetFromMap(new IdentityHashMap<String, Boolean>());
    uniqueStrings.addAll(Arrays.asList(strings2));
    System.out.printf("Unique strings %,d%n", uniqueStrings.size());

    long start = System.nanoTime();
    HashMap<String,String> map = new HashMap();
    for(String s: strings2)
        map.put(s,s);
    long time = System.nanoTime() - start;
    System.out.printf("Took %.3f second to map strings%n", time/1e9);

    testHashArray(strings2, 10192);
    testHashArray(strings2, 101929);
    testHashArray(strings2, 1019291);
}

печать

Original size 353,293,201
Unique strings 766,222
Took 0.979 second to map strings
Hash size: 10,192 took 0.357 second. Hit/miss 5,213,210/4,786,790 
Hash size: 101,929 took 0.309 second. Hit/miss 7,202,094/2,797,906 
Hash size: 1,019,291 took 0.254 second. Hit/miss 8,789,382/1,210,618 

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

Вам действительно нужно сэкономить 130 МБ? Я знаю, это звучит великолепно, но будет ли память использоваться для чего-то еще?

Если вам нужна более быстрая форма для intern (), вы можете использовать массив фиксированного размера.

0 голосов
/ 02 ноября 2011

Вот еще одна мысль, хотя она может звучать немного на кулинарной стороне. Задумывались ли вы о том, чтобы просто написать генератор кода, который просто анализирует ваш XML-файл и выплевывает Java-код, который заполняет карту с использованием фактических строк (которые интернируются во время компиляции)

Примерно так

public final class ConfigurationData {
  public static String get(String key) {
    return map.get(key);
  }
  private static final Map<String,String> MAP;
  static {
    MAP = new HashMap<String,String>([[[ number of records to load up ]]]);
    MAP.put([[[key 1]]], [[[ value 1 ]]]);
    MAP.put([[[key 2]]], [[[ value 2 ]]]);
    ...
  }
}

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

0 голосов
/ 28 июля 2011

У нас возникла проблема с анализом строки в проверенном объекте Name. Это было сделано повсеместно в приложении и должно было быть оптимизировано как по памяти, так и по скорости.

После нескольких тестовых прогонов мы в итоге получили решение, обрабатывающее массивы символов как во время синтаксического анализа, так и при реализации Name.

String.toCharArray () , чтобы получить массив строки, или можно использовать String.charAt (pos) . Для быстрого копирования между массивами мы использовали System.arrayCopy .

На самом деле синтаксический анализ выполнялся быстрее, чем использование кэша для поиска.

...