Компактные строки в 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.