Compacte snaren in Java 9

1. Overzicht

Snaren in Java worden intern vertegenwoordigd door een char [] met de karakters van de Draad. En elk char bestaat uit 2 bytes omdat Java maakt intern gebruik van UTF-16.

Als bijvoorbeeld een Draad bevat een woord in de Engelse taal, de eerste 8 bits zijn allemaal 0 voor elke char, omdat een ASCII-teken kan worden weergegeven met een enkele byte.

Veel tekens hebben 16 bits nodig om ze weer te geven, maar statistisch gezien hebben de meeste slechts 8 bits nodig - LATIN-1 tekenweergave. Er is dus ruimte om het geheugengebruik en de prestaties te verbeteren.

Wat ook belangrijk is, is dat Draads nemen doorgaans een groot deel van de JVM-heapruimte in beslag. En vanwege de manier waarop ze worden opgeslagen door de JVM, in de meeste gevallen een Draad instantie kan dubbel opnemen ruimte het heeft eigenlijk nodig.

In dit artikel bespreken we de optie Compressed String, geïntroduceerd in JDK6, en de nieuwe Compact String, onlangs geïntroduceerd met JDK9. Beide zijn ontworpen om het geheugengebruik van strings op de JMV te optimaliseren.

2. Gecomprimeerd Draad - Java 6

De JDK 6 update 21 Performance Release introduceerde een nieuwe VM-optie:

-XX: + UseCompressedStrings

Als deze optie is ingeschakeld, Snaren worden opgeslagen als byte[], in plaats van char [] - waardoor veel geheugen wordt bespaard. Deze optie werd uiteindelijk echter verwijderd in JDK 7, voornamelijk omdat het een aantal onbedoelde gevolgen voor de prestaties had.

3. Compact Draad - Java 9

Java 9 heeft het concept compact gebracht Snaren back.

Dit betekent dat wanneer we een Draad als alle karakters van de Draad kan worden weergegeven met behulp van een byte - LATIN-1-weergave, wordt een byte-array gebruikt intern, zodat één byte wordt gegeven voor één teken.

In andere gevallen, als een teken meer dan 8 bits nodig heeft om het weer te geven, worden alle tekens opgeslagen met twee bytes voor elk - UTF-16-weergave.

Dus in principe gebruikt het waar mogelijk slechts één byte voor elk teken.

Nu is de vraag - hoe zullen alle Draad operaties werken? Hoe zal het onderscheid maken tussen de LATIN-1- en UTF-16-representaties?

Om dit probleem aan te pakken, wordt er nog een wijziging aangebracht in de interne implementatie van het Draad. We hebben een laatste veld coder, dat deze informatie bewaart.

3.1. Draad Implementatie in Java 9

Tot nu toe heeft de Draad is opgeslagen als een char []:

private finale char [] waarde;

Vanaf nu wordt het een byte[]:

private laatste byte [] waarde;

De variabele coder:

privé laatste byte coder;

Waar de coder kan zijn:

statische laatste byte LATIN1 = 0; statische laatste byte UTF16 = 1;

Meeste van de Draad operaties controleren nu de coder en sturen naar de specifieke implementatie:

openbare int indexOf (int ch, int fromIndex) {return isLatin1 ()? StringLatin1.indexOf (waarde, ch, fromIndex): StringUTF16.indexOf (waarde, ch, fromIndex); } private boolean isLatin1 () {return COMPACT_STRINGS && coder == LATIN1; } 

Met alle informatie die de JVM nodig heeft klaar en beschikbaar, de CompactString De VM-optie is standaard ingeschakeld. Om het uit te schakelen, kunnen we gebruiken:

+ XX: -CompactStrings

3.2. Hoe coder Werken

In Java 9 Draad klasse implementatie, wordt de lengte berekend als:

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

Als het Draad bevat alleen LATIN-1, de waarde van de coder zal 0 zijn, dus de lengte van de Draad zal hetzelfde zijn als de lengte van de byte-array.

In andere gevallen, als het Draad is in UTF-16-weergave, de waarde van coder is 1, en daarom is de lengte de helft van de werkelijke byte-array.

Merk op dat alle wijzigingen die zijn aangebracht voor Compact Draad, zijn in de interne implementatie van de Draad class en zijn volledig transparant voor ontwikkelaars die Draad.

4. Compact Snaren versus gecomprimeerd Snaren

In het geval van JDK 6 gecomprimeerd Snaren, een groot probleem was dat de Draad constructor alleen geaccepteerd char [] als argument. Daarnaast zijn er veel Draad operaties waren afhankelijk van char [] representatie en niet een byte-array. Hierdoor moest er veel uitgepakt worden, wat de prestaties nadelig beïnvloedde.

Terwijl in het geval van Compact Draad, Het behouden van het extra veld “coder” kan ook de overhead verhogen. Om de kosten van het coder en het uitpakken van bytes naar chars (in het geval van UTF-16-representatie), zijn sommige methoden geïntegreerd en is de ASM-code die door de JIT-compiler wordt gegenereerd, ook verbeterd.

Deze verandering resulteerde in een aantal contra-intuïtieve resultaten. De LATIN-1 indexOf (tekenreeks) roept een intrinsieke methode aan, terwijl de indexOf (teken) doet niet. In het geval van UTF-16 roepen beide methoden een intrinsieke methode aan. Dit probleem heeft alleen betrekking op de LATIN-1 Draad en zal in toekomstige releases worden opgelost.

Dus Compact Snaren zijn beter dan de gecomprimeerde Snaren in termen van prestaties.

Om erachter te komen hoeveel geheugen er wordt bespaard met de Compact Snaren, verschillende heap-dumps van Java-applicaties zijn geanalyseerd. En hoewel de resultaten sterk afhankelijk waren van de specifieke toepassingen, waren de algehele verbeteringen bijna altijd aanzienlijk.

4.1. Verschil in prestaties

Laten we eens kijken naar een heel eenvoudig voorbeeld van het prestatieverschil tussen Compact inschakelen en uitschakelen Snaren:

lange startTime = System.currentTimeMillis (); Lijststrings = 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 toegevoegd = (String) strings.stream () .limit (100_000) .reduce ("", (l, r) -> l.toString () + r.toString ()); totalTime = System.currentTimeMillis () - startTime; System.out.println ("Aangemaakte string van lengte" + appended.length () + "in" + totalTime + "ms.");

Hier creëren we 10 miljoen Draads en ze vervolgens op een naïeve manier toevoegen. Wanneer we deze code uitvoeren (Compact Strings zijn standaard ingeschakeld), krijgen we de uitvoer:

Gegenereerde 10000000 strings in 854 ms. String gemaakt met een lengte van 488895 in 5130 ms.

Evenzo, als we het uitvoeren door de Compact Strings uit te schakelen met: -XX: -CompactStrings optie, de output is:

Gegenereerde 10000000 strings in 936 ms. String gemaakt met een lengte van 488895 in 9727 ms.

Het is duidelijk dat dit een oppervlaktetest is en het kan niet erg representatief zijn - het is slechts een momentopname van wat de nieuwe optie kan doen om de prestaties in dit specifieke scenario te verbeteren.

5. Conclusie

In deze tutorial zagen we de pogingen om de prestaties en het geheugengebruik op de JVM te optimaliseren - door op te slaan Draads op een geheugenefficiënte manier.

Zoals altijd is de volledige code beschikbaar op Github.


$config[zx-auto] not found$config[zx-overlay] not found