Kompakte strenger i Java 9

1. Oversikt

Strenger i Java er internt representert av en røye [] inneholder tegnene i String. Og hver røye består av 2 byte fordi Java bruker UTF-16 internt.

For eksempel hvis en String inneholder et ord på engelsk, vil de ledende 8 bitene være 0 for hver røye, som et ASCII-tegn kan vises med en enkelt byte.

Mange tegn krever 16 bits for å representere dem, men statistisk sett krever de fleste bare 8 bits - LATIN-1 tegnrepresentasjon. Så det er mulighet for å forbedre minneforbruket og ytelsen.

Det som også er viktig er at Strings opptar vanligvis en stor andel av JVM-haugarealet. Og på grunn av måten de er lagret av JVM, i de fleste tilfeller, en String forekomst kan ta dobbelt rom det trenger faktisk.

I denne artikkelen vil vi diskutere Compressed String-alternativet, introdusert i JDK6 og den nye Compact String, nylig introdusert med JDK9. Begge disse ble designet for å optimalisere minneforbruk av Strings på JMV.

2. Komprimert String - Java 6

JDK 6 oppdatering 21 Performance Release, introduserte et nytt VM-alternativ:

-XX: + UseCompressedStrings

Når dette alternativet er aktivert, Strenger lagres som byte [], i stedet for røye [] - dermed sparer du mye minne. Imidlertid ble dette alternativet til slutt fjernet i JDK 7, hovedsakelig fordi det hadde noen utilsiktede ytelseskonsekvenser.

3. Kompakt String - Java 9

Java 9 har tatt med begrepet kompakt Strenger back.

Dette betyr at når vi oppretter en String hvis alle tegnene i String kan representeres ved hjelp av en byte - LATIN-1-representasjon, en byteoppstilling vil bli brukt internt, slik at en byte er gitt for ett tegn.

I andre tilfeller, hvis et tegn krever mer enn 8-bits for å representere det, lagres alle tegnene ved hjelp av to byte for hver - UTF-16-representasjon.

Så i utgangspunktet, når det er mulig, vil den bare bruke en enkelt byte for hver karakter.

Nå er spørsmålet - hvordan vil alle String operasjoner fungerer? Hvordan vil det skille mellom LATIN-1 og UTF-16 representasjonene?

Vel, for å takle dette problemet, blir det gjort en annen endring i den interne implementeringen av String. Vi har et siste felt koder, som bevarer denne informasjonen.

3.1. String Implementering i Java 9

Inntil nå har String ble lagret som en røye []:

privat slutttegn [] verdi;

Fra nå av blir det en byte []:

privat sluttbyte [] verdi;

Variabelen koder:

privat sluttbytekoder;

Hvor i koder kan være:

statisk sluttbyte LATIN1 = 0; statisk sluttbyte UTF16 = 1;

Mesteparten av String operasjoner sjekker nå koderen og sender til den spesifikke implementeringen:

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

Med all informasjon JVM trenger klar og tilgjengelig, CompactString VM-alternativet er aktivert som standard. For å deaktivere det, kan vi bruke:

+ XX: -CompactStrings

3.2. Hvordan koder Virker

I Java 9 String klasseimplementering, beregnes lengden som:

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

Hvis den String inneholder bare LATIN-1, verdien av koder vil være 0 så lengden på String vil være den samme som lengden på byteoppstillingen.

I andre tilfeller, hvis String er i UTF-16 representasjon, verdien av koder vil være 1, og dermed vil lengden være halvparten av størrelsen på den faktiske byteoppstillingen.

Merk at alle endringene som er gjort for Compact Streng, er i den interne implementeringen av String klasse og er helt gjennomsiktige for utviklere som bruker String.

4. Kompakt Strenger vs. komprimert Strenger

I tilfelle JDK 6 komprimert Strenger, et stort problem overfor var at String kun konstruktør akseptert røye [] som argument. I tillegg til dette, mange String operasjoner var avhengig av røye [] representasjon og ikke et byte-utvalg. På grunn av dette måtte mye utpakking gjøres, noe som påvirket ytelsen.

Mens det gjelder Compact Streng, ved å opprettholde det ekstra felt "koderen" kan også øke overhead. For å redusere kostnadene for koder og utpakking av bytes til røyes (i tilfelle UTF-16-representasjon), er noen av metodene intrinsifisert, og ASM-koden generert av JIT-kompilatoren er også forbedret.

Denne endringen resulterte i noen kontraintuitive resultater. LATIN-1 indexOf (streng) kaller en egen metode, mens indexOf (char) gjør ikke. I tilfelle UTF-16 kaller begge disse metodene en egen metode. Dette problemet berører bare LATIN-1 String og vil bli løst i fremtidige utgivelser.

Dermed kompakt Strenger er bedre enn komprimert Strenger når det gjelder ytelse.

For å finne ut hvor mye minne som er lagret ved hjelp av Compact Strenger, forskjellige Java-applikasjonsdumper ble analysert. Og mens resultatene var sterkt avhengige av de spesifikke applikasjonene, var de generelle forbedringene nesten alltid betydelige.

4.1. Forskjell i ytelse

La oss se et veldig enkelt eksempel på ytelsesforskjellen mellom å aktivere og deaktivere Compact Strenger:

lang starttid = System.currentTimeMillis (); Listestrenger = IntStream.rangeClosed (1, 10_000_000) .mapToObj (Heltall :: toString) .collect (toList ()); lang totalTime = System.currentTimeMillis () - starttid; 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 () - starttid; System.out.println ("Laget streng av lengde" + appended.length () + "i" + totalTime + "ms.");

Her skaper vi 10 millioner Strings og deretter legge dem på en naiv måte. Når vi kjører denne koden (kompakte strenger er aktivert som standard), får vi utdataene:

Genererte 10000000 strenger på 854 ms. Opprettet streng med lengde 488895 i 5130 ms.

På samme måte, hvis vi kjører den ved å deaktivere kompakte strenger ved hjelp av: -XX: -CompactStrings alternativ, er utgangen:

Genererte 10000000 strenger på 936 ms. Opprettet streng med lengde 488895 i 9727 ms.

Det er klart at dette er en overflatenivå-test, og det kan ikke være veldig representativt - det er bare et øyeblikksbilde av hva det nye alternativet kan gjøre for å forbedre ytelsen i dette spesielle scenariet.

5. Konklusjon

I denne veiledningen så vi forsøkene på å optimalisere ytelsen og minneforbruket på JVM - ved å lagre Strings på en minneeffektiv måte.

Som alltid er hele koden tilgjengelig på Github.


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