Kompakte strenge i Java 9

1. Oversigt

Strenge i Java er internt repræsenteret af en char [] der indeholder tegnene i Snor. Og hver char består af 2 byte, fordi Java bruger internt UTF-16.

For eksempel, hvis en Snor indeholder et ord på engelsk, de førende 8 bits vil alle være 0 for hver char, da et ASCII-tegn kan repræsenteres ved hjælp af en enkelt byte.

Mange tegn kræver 16 bits for at repræsentere dem, men statistisk set kræver de fleste kun 8 bits - LATIN-1 tegnrepræsentation. Så der er mulighed for at forbedre hukommelsesforbruget og ydeevnen.

Hvad der også er vigtigt er det Snors optager normalt en stor del af JVM-bunkerummet. Og på grund af den måde, de lagres på af JVM, i de fleste tilfælde -en Snor eksempel kan tage dobbelt plads det har faktisk brug for.

I denne artikel diskuterer vi komprimeret strengindstilling, der blev introduceret i JDK6 og den nye kompakte streng, der for nylig blev introduceret med JDK9. Begge disse blev designet til at optimere hukommelsesforbruget af Strings på JMV.

2. Komprimeret Snor - Java 6

JDK 6 opdatering 21 Performance Release introducerede en ny VM-mulighed:

-XX: + UseCompressedStrings

Når denne indstilling er aktiveret, Strenge er gemt som byte [], i stedet for char [] - dermed sparer en masse hukommelse. Denne mulighed blev dog til sidst fjernet i JDK 7, hovedsageligt fordi den havde nogle utilsigtede præstationskonsekvenser.

3. Kompakt Snor - Java 9

Java 9 har bragt begrebet kompakt Strenge back.

Det betyder at når vi opretter en Snor hvis alle tegn i Snor kan repræsenteres ved hjælp af en byte - LATIN-1-repræsentation, en byte-array bruges internt, således at der gives en byte for et tegn.

I andre tilfælde, hvis et tegn kræver mere end 8 bit for at repræsentere det, gemmes alle tegnene ved hjælp af to bytes til hver - UTF-16-repræsentation.

Så grundlæggende, når det er muligt, bruger den bare en enkelt byte til hvert tegn.

Nu er spørgsmålet - hvordan vil alt det Snor operationer arbejde? Hvordan skelnes der mellem repræsentationerne LATIN-1 og UTF-16?

For at tackle dette spørgsmål foretages en anden ændring af den interne implementering af programmet Snor. Vi har et sidste felt koder, der bevarer disse oplysninger.

3.1. Snor Implementering i Java 9

Indtil nu har Snor blev gemt som en char []:

privat endelig char [] værdi;

Fra nu af bliver det en byte []:

privat slutbyte [] værdi;

Variablen koder:

privat slutbyte-koder;

Hvor er koder måske:

statisk endelig byte LATIN1 = 0; statisk slutbyte UTF16 = 1;

Det meste af Snor operationer kontrollerer nu koderen og afsender til den specifikke implementering:

public int indexOf (int ch, int fromIndex) {return erLatin1 ()? StringLatin1.indexOf (værdi, ch, fraIndex): StringUTF16.indexOf (værdi, ch, fraIndex); } privat boolsk isLatin1 () {return COMPACT_STRINGS && koder == LATIN1; } 

Med alle de oplysninger, JVM har brug for klar og tilgængelig, er CompactString VM-indstilling er aktiveret som standard. For at deaktivere det kan vi bruge:

+ XX: -CompactStrings

3.2. Hvordan koder Arbejder

I Java 9 Snor klasseimplementering beregnes længden som:

public int længde () {return value.length >> koder; }

Hvis den Snor indeholder kun LATIN-1, værdien af koder vil være 0, så længden af Snor vil være den samme som længden af ​​byte-arrayet.

I andre tilfælde, hvis Snor er i UTF-16 repræsentation, værdien af koder vil være 1, og dermed vil længden være halvt så stor som den aktuelle byte-array.

Bemærk, at alle ændringer foretaget for Compact Snor, er i den interne implementering af programmet Snor klasse og er fuldt gennemsigtige for udviklere, der bruger Snor.

4. Kompakt Strenge vs. komprimeret Strenge

I tilfælde af JDK 6 komprimeret Strenge, et stort problem står overfor var, at Snor kun konstruktør accepteret char [] som et argument. Ud over dette, mange Snor operationer var afhængige af char [] repræsentation og ikke et byte-array. På grund af dette måtte der udføres meget udpakning, hvilket påvirkede præstationen.

Derimod i tilfælde af Compact Snor, vedligeholdelse af det ekstra felt "koder" kan også øge omkostningerne. For at mindske omkostningerne ved koder og udpakning af bytes til chars (i tilfælde af UTF-16-repræsentation) er nogle af metoderne iboende, og ASM-koden genereret af JIT-kompilatoren er også blevet forbedret.

Denne ændring resulterede i nogle kontraintuitive resultater. LATIN-1 indexOf (streng) kalder en iboende metode, mens indexOf (char) gør ikke. I tilfælde af UTF-16 kalder begge disse metoder en iboende metode. Dette problem berører kun LATIN-1 Snor og vil blive rettet i fremtidige udgivelser.

Således kompakt Strenge er bedre end de komprimerede Strenge med hensyn til ydeevne.

For at finde ud af, hvor meget hukommelse der er gemt ved hjælp af Compact Strenge, forskellige Java-applikations bunke-lossepladser blev analyseret. Og mens resultaterne var stærkt afhængige af de specifikke applikationer, var de samlede forbedringer næsten altid betydelige.

4.1. Forskel i ydeevne

Lad os se et meget simpelt eksempel på præstationsforskellen mellem aktivering og deaktivering af Compact Strenge:

lang starttid = System.currentTimeMillis (); List strings = IntStream.rangeClosed (1, 10_000_000) .mapToObj (Integer :: toString) .collect (toList ()); lang totalTime = System.currentTimeMillis () - startTime; System.out.println ("Genereret" + strings.size () + "strings i" + 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 ("Oprettet streng af længde" + appended.length () + "i" + totalTime + "ms.");

Her skaber vi 10 millioner Snors og derefter tilføje dem på en naiv måde. Når vi kører denne kode (Compact Strings er aktiveret som standard), får vi output:

Genereret 10000000 strenge i 854 ms. Oprettet streng af længde 488895 i 5130 ms.

Tilsvarende, hvis vi kører det ved at deaktivere kompakte strenge ved hjælp af: -XX: -CompactStrings option, output er:

Genererede 10000000 strenge i 936 ms. Oprettet streng af længde 488895 i 9727 ms.

Det er klart, at dette er en overfladetesttest, og det kan ikke være meget repræsentativt - det er kun et øjebliksbillede af, hvad den nye mulighed kan gøre for at forbedre ydeevnen i dette særlige scenario.

5. Konklusion

I denne vejledning så vi forsøgene på at optimere ydeevnen og hukommelsesforbruget på JVM - ved at gemme Snors på en hukommelseseffektiv måde.

Som altid er hele koden tilgængelig på Github.


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