Компактные строки в Java 9

Компактные строки в Java 9

1. обзор

Strings в Java внутренне представленыchar[], содержащим символыString. И каждыйchar состоит из 2 байтов, потому чтоJava internally uses UTF-16.

Например, еслиString содержит слово на английском языке, все ведущие 8 бит будут равны 0 для каждогоchar, поскольку символ ASCII может быть представлен с использованием одного байта.

Многим символам требуется 16 бит для их представления, но статистически для большинства требуется всего 8 бит - представление символов LATIN-1. Таким образом, существует возможность улучшить потребление памяти и производительность.

Также важно то, чтоStrings обычно занимает большую часть пространства кучи JVM. И из-за того, как они хранятся в JVM, в большинстве случаевa String instance can take up double пробелit actually needs.

В этой статье мы обсудим опцию Compressed String, представленную в JDK6, и новую Compact String, недавно представленную в JDK9. Оба из них были разработаны для оптимизации потребления памяти строк в JMV.

2. СжатыйString - Java 6

В обновлении JDK 6 21 Performance Release представлена ​​новая опция виртуальной машины:

-XX:+UseCompressedStrings

Когда эта опция включена,Strings сохраняются какbyte[] вместоchar[] –, таким образом, экономится много памяти. Однако эта опция была в конечном итоге удалена в JDK 7, главным образом из-за непредвиденных последствий для производительности.

3. КомпактныйString - Java 9

В Java 9 появилась концепция компактногоStrings back.

Это означает, чтоwhenever we create a String if all the characters of the String can be represented using a byte — LATIN-1 representation, a byte array will be used внутренне, так что один байт дается для одного символа.

В других случаях, если какой-либо символ требует более 8 бит для его представления, все символы сохраняются с использованием двух байтов для каждого - представления UTF-16.

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

Теперь возникает вопрос: как будут работать все операцииString? Как будет различаться представление LATIN-1 и UTF-16?

Что ж, чтобы решить эту проблему, во внутреннюю реализациюString было внесено еще одно изменение. У нас есть последнее полеcoder, которое сохраняет эту информацию.

3.1. String Реализация в Java 9

До сих порString хранился какchar[]:

private final char[] value;

С этого момента это будетbyte[]:

private final byte[] value;

Переменнаяcoder:

private final byte coder;

Гдеcoder может быть:

static final byte LATIN1 = 0;
static final byte UTF16 = 1;

Большинство операцийString теперь проверяют кодировщик и отправляются в конкретную реализацию:

public int indexOf(int ch, int fromIndex) {
    return isLatin1()
      ? StringLatin1.indexOf(value, ch, fromIndex)
      : StringUTF16.indexOf(value, ch, fromIndex);
}

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

Когда вся необходимая JVM информация готова и доступна, опцияCompactString VM включена по умолчанию. Чтобы отключить его, мы можем использовать:

+XX:-CompactStrings

3.2. Как работаетcoder

В реализации класса Java 9String длина рассчитывается как:

public int length() {
    return value.length >> coder;
}

ЕслиString содержит только LATIN-1, значениеcoder будет равно 0, поэтому длинаString будет такой же, как длина массива байтов.

В других случаях, еслиString находится в представлении UTF-16, значениеcoder будет равно 1, и, следовательно, длина будет вдвое меньше размера фактического байтового массива.

Обратите внимание, что все изменения, внесенные для CompactString,, относятся к внутренней реализации классаString и полностью прозрачны для разработчиков, использующихString.

4. КомпактныйStrings vs. СжатыйStrings

В случае JDK 6 CompressedStrings, основная проблема заключалась в том, что конструкторString принимал толькоchar[] в качестве аргумента. В дополнение к этому, многие операцииString зависели от представленияchar[], а не от массива байтов. Из-за этого пришлось много распаковывать, что сказывалось на производительности.

В то время как в случае CompactString, поддержание дополнительного поля «кодер» также может увеличить накладные расходы. Чтобы снизить стоимостьcoder и распаковкиbytes вchars (в случае представления UTF-16), некоторые из методов -intrinsified и код ASM генерируемый JIT-компилятором также был улучшен.

Это изменение привело к некоторым нелогичным результатам. LATIN-1indexOf(String) вызывает внутренний метод, аindexOf(char) - нет. В случае UTF-16 оба эти метода вызывают встроенный метод. Эта проблема затрагивает только LATIN-1String и будет исправлена ​​в будущих выпусках.

Таким образом, CompactStrings лучше, чем CompressedStrings с точки зрения производительности.

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

4.1. Разница в производительности

Давайте посмотрим на очень простой пример разницы в производительности между включением и отключением CompactStrings:.

long startTime = System.currentTimeMillis();

List strings = IntStream.rangeClosed(1, 10_000_000)
  .mapToObj(Integer::toString)
  .collect(toList());

long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
  "Generated " + strings.size() + " strings in " + totalTime + " ms.");

startTime = System.currentTimeMillis();

String appended = (String) strings.stream()
  .limit(100_000)
  .reduce("", (l, r) -> l.toString() + r.toString());

totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length()
  + " in " + totalTime + " ms.");

Здесь мы создаем 10 миллионовStrings, а затем добавляем их наивным образом. Когда мы запускаем этот код (Compact Strings включены по умолчанию), мы получаем вывод:

Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.

Точно так же, если мы запустим его, отключив Compact Strings с помощью параметра:-XX:-CompactStrings, на выходе получим:

Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.

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

5. Заключение

В этом руководстве мы увидели попытки оптимизировать производительность и потребление памяти на JVM - путем сохраненияStrings с эффективным использованием памяти.

Как всегда доступен весь кодover on Github.