Strings in Java komprimieren 9

Kompakte Zeichenfolgen in Java 9

1. Überblick

Strings in Java werden intern durchchar[] dargestellt, die die Zeichen vonString enthalten. Und jedeschar besteht aus 2 Bytes, weilJava internally uses UTF-16.

Wenn beispielsweise einString ein Wort in der englischen Sprache enthält, sind die führenden 8 Bits alle 0 für jedeschar, da ein ASCII-Zeichen mit einem einzelnen Byte dargestellt werden kann.

Für viele Zeichen sind 16 Bit erforderlich, für die meisten statistisch jedoch nur 8 Bit - LATIN-1-Zeichendarstellung. Es gibt also Möglichkeiten, den Speicherverbrauch und die Leistung zu verbessern.

Wichtig ist auch, dassStrings normalerweise einen großen Teil des JVM-Heap-Speicherplatzes einnimmt. Und aufgrund der Art und Weise, wie sie von der JVM gespeichert werden, ist in den meisten Fällena String instance can take up double Speicherplatzit actually needs.

In diesem Artikel werden wir die in JDK6 eingeführte Option für komprimierte Zeichenfolgen und die kürzlich mit JDK9 eingeführte neue kompakte Zeichenfolge erläutern. Beide wurden entwickelt, um den Speicherverbrauch von Strings auf dem JMV zu optimieren.

2. KomprimierteString - Java 6

Mit dem Performance Release von JDK 6 Update 21 wurde eine neue VM-Option eingeführt:

-XX:+UseCompressedStrings

Wenn diese Option aktiviert ist, werdenStrings alsbyte[] anstelle vonchar[] – gespeichert, wodurch viel Speicherplatz gespart wird. Diese Option wurde jedoch schließlich in JDK 7 entfernt, hauptsächlich weil sie einige unbeabsichtigte Auswirkungen auf die Leistung hatte.

3. KompaktString - Java 9

Java 9 hat das Konzept der kompaktenStrings back. eingeführt

Dies bedeutet, dasswhenever 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 usedintern ist, so dass ein Byte für ein Zeichen angegeben wird.

In anderen Fällen werden alle Zeichen mit jeweils zwei Bytes gespeichert, wenn für die Darstellung eines Zeichens mehr als 8 Bits erforderlich sind - UTF-16-Darstellung.

Wann immer möglich, wird also nur ein einziges Byte für jedes Zeichen verwendet.

Die Frage ist nun: Wie funktionieren alle Operationen vonString? Wie wird zwischen den Darstellungen LATIN-1 und UTF-16 unterschieden?

Um dieses Problem anzugehen, wird eine weitere Änderung an der internen Implementierung derStringvorgenommen. Wir haben ein letztes Feldcoder, das diese Informationen beibehält.

3.1. String Implementierung in Java 9

Bisher wurdeString alschar[] gespeichert:

private final char[] value;

Von nun an ist es einbyte[]:

private final byte[] value;

Die Variablecoder:

private final byte coder;

Wo diecoder sein können:

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

Die meistenString-Operationen überprüfen jetzt den Codierer und senden ihn an die spezifische Implementierung:

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;
}

Mit allen Informationen, die die JVM benötigt, ist die VM-OptionCompactStringtandardmäßig aktiviert. Zum Deaktivieren können wir Folgendes verwenden:

+XX:-CompactStrings

3.2. Wiecoder funktioniert

In der Klassenimplementierung von Java 9Stringwird die Länge wie folgt berechnet:

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

WennString nur LATIN-1 enthält, ist der Wert voncoder 0, sodass die Länge vonString der Länge des Bytearrays entspricht.

In anderen Fällen, wennString in UTF-16-Darstellung vorliegt, ist der Wert voncoder 1, und daher ist die Länge halb so groß wie das tatsächliche Byte-Array.

Beachten Sie, dass alle für CompactString, vorgenommenen Änderungen in der internen Implementierung der KlasseString enthalten sind und für Entwickler, dieString verwenden, vollständig transparent sind.

4. KompakteStrings vs. KomprimierteStrings

Im Fall von JDK 6 CompressedStrings, bestand ein Hauptproblem darin, dass der Konstruktor vonStringnurchar[] als Argument akzeptierte. Darüber hinaus hingen vieleString-Operationen von derchar[]-Darstellung ab und nicht von einem Byte-Array. Aus diesem Grund musste viel ausgepackt werden, was die Leistung beeinträchtigte.

Während bei CompactString, das Beibehalten des zusätzlichen Felds "Codierer" auch den Overhead erhöhen kann. Um die Kosten fürcoder und das Entpacken vonbytes inchars (im Fall einer UTF-16-Darstellung) zu verringern, sind einige der Methodenintrinsified und der ASM-Code Der vom JIT-Compiler generierte wurde ebenfalls verbessert.

Diese Änderung führte zu kontraintuitiven Ergebnissen. Das LATIN-1indexOf(String) ruft eine intrinsische Methode auf, dasindexOf(char) nicht. Im Fall von UTF-16 rufen beide Methoden eine intrinsische Methode auf. Dieses Problem betrifft nur die LATIN-1String und wird in zukünftigen Versionen behoben.

Somit sind kompakteStrings in Bezug auf die Leistung besser als die komprimiertenStrings.

Um herauszufinden, wie viel Speicher mit dem CompactStrings,gespart wird, wurden die verschiedenen Java-Anwendungs-Heap-Dumps analysiert. Und obwohl die Ergebnisse stark von den spezifischen Anwendungen abhängig waren, waren die Gesamtverbesserungen fast immer beträchtlich.

4.1. Leistungsunterschied

Sehen wir uns ein sehr einfaches Beispiel für den Leistungsunterschied zwischen dem Aktivieren und Deaktivieren von CompactStrings:an

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.");

Hier erstellen wir 10 MillionenStrings und hängen sie dann auf naive Weise an. Wenn wir diesen Code ausführen (Compact Strings sind standardmäßig aktiviert), erhalten wir die Ausgabe:

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

Wenn wir es ausführen, indem wir die Compact Strings mit der Option:-XX:-CompactStringsdeaktivieren, lautet die Ausgabe ebenfalls:

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

Dies ist eindeutig ein Test auf Oberflächenebene und kann nicht sehr repräsentativ sein. Es ist nur eine Momentaufnahme dessen, was die neue Option zur Verbesserung der Leistung in diesem speziellen Szenario bewirken kann.

5. Fazit

In diesem Tutorial haben wir die Versuche gesehen, die Leistung und den Speicherverbrauch in der JVM zu optimieren - indemStrings speichereffizient gespeichert wurden.

Wie immer ist der gesamte Codeover on Github verfügbar.