2017-02-07 7 views
8

Java 8 wprowadziła deduplikację ciągów, którą można włączyć, uruchamiając maszynę JVM z opcją -XX:+UseStringDeduplication, pozwalającą zaoszczędzić trochę pamięci, odwołując się do podobnych obiektów String zamiast zachować duplikaty. Oczywiście jego skuteczność różni się w zależności od programu, w zależności od wykorzystania Strings, ale myślę, że można bezpiecznie powiedzieć, że ogólnie rzecz biorąc jest to korzystne dla większości aplikacji (jeśli nie wszystkie), co powoduje, że zastanawiam się nad kilkoma rzeczami:Dlaczego/Kiedy nie chciałbyś mieć włączonej Java 8 UseStringDeduplication w JVM?

Dlaczego czy nie jest domyślnie włączona? Czy to ze względu na koszty związane z deduplikacją, czy po prostu dlatego, że G1GC jest wciąż uważany za nowy?

Czy istnieją (lub mogłyby występować) przypadki skrajne, w których nie chcesz korzystać z deduplikacji?

+3

Myślę, że to jest (jak się domyślacie) * głównie * koszty runtime de-duplikacji. –

+0

Nie G1GC, ale sama deduplikacja może zostać uznana za nową. – Holger

Odpowiedz

13

Przypadki String deduplikacji może być szkodliwe to:

  • Wiele strun ale bardzo niskie prawdopodobieństwo duplikaty: napowietrznej czas szuka duplikatów, a czas i przestrzeń narzut de udanego zwodzenia hashtable nie zostanie spłacone.
  • Rozsądne prawdopodobieństwo duplikatów, ale większość napisów umiera w ciągu kilku cykli GC. : Deduplikacja ma znacznie mniejszą korzyść, jeśli i tak niedługo zostaną usunięte GC.

(W drugim przypadku nie chodzi o ciągi, które nie przeżywają pierwszy cykl GC. To nie ma sensu dla GC nawet spróbować de-dup struny, że wie się śmieci).

Możemy jedynie spekulować, dlaczego zespół Javy domyślnie nie włącza usuwania duplikatów, ale są oni w znacznie lepszej pozycji, aby podejmować racjonalne decyzje (tzn. Oparte na dowodach) na tym, że ty i ja. Rozumiem, że mają dostęp do wielu dużych aplikacji w świecie rzeczywistym do testowania/testowania efektów optymalizacji. Mogą również mieć głębokie kontakty z wieloma organizacjami partnerskimi lub klientami o podobnie dużych bazach kodów i obawach związanych z efektywnością ... o to, kogo mogliby prosić o informacje na temat tego, które optymalizacje naprawdę działają.

1 - To zależy od ustawienia JVM StringDeduplicationAgeThreshold . Domyślnie jest to 3, co oznacza, że ​​(w przybliżeniu) ciąg musi przetrwać 3 mniejsze kolekcje lub większą kolekcję, którą należy rozważyć w celu usunięcia duplikatu. Ale w każdym razie, jeśli ciąg zostanie zdublowany, a następnie okaże się, że nieosiągalny niedługo potem, koszty usuwania duplikatów nie zostaną spłacone za ten ciąg.


Jeśli proszą, kiedy należy rozważyć włączenie de duping, moja rada to aby spróbować i zobaczyć, czy to pomaga na zasadzie per-application. Ale musisz wykonać pewne testy porównawcze na poziomie aplikacji (co wymaga wysiłku!), Aby upewnić się, że usuwanie duplikatów jest korzystne ...

Ostrożna lektura pod numerem JEP 192 pomoże również w zrozumieniu problemów i dokonaniu oceny o tym, jak mogą ubiegać się o aplikację Java.

+0

Drugi przypadek nie jest w pełni prawdziwy. Ponieważ deduplikuje tylko łańcuchy, które przetrwały 3 GC. Znalazłem tę stronę jako dobrą lekturę na ten temat http://java-performance.info/java-string-deduplication/ – keiki

+1

Jeśli struna przetrwa 3 GC, zostanie zdmuchnięta, a następnie stanie się nieosiągalna wkrótce po jej usunięciu koszty ogólne nie zostaną odzyskane. To był mój punkt widzenia. –

10

absolutnie zrozumieć, że to nie jest odpowiedź na pytanie, po prostu chciałem wspomnieć, że jdk-9 wprowadza jeszcze jedną optymalizację, która jest domyślnie nazwie:

-XX: + CompactStrings

gdzie Latin1 znaków zajmują jeden bajt zamiast dwóch (przez znak). Z powodu tej zmiany wiele wewnętrznych metod String zmieniło się - działają one tak samo dla użytkownika, ale wewnętrznie są szybsze w wielu przypadkach.

Również w przypadku Ciągów do łączenia dwóch ciągów razem za pośrednictwem znaku plus, javac wygeneruje inny kod bajtowy.

Brak instrukcji kodu bajtowego, który łączy dwa ciągi razem więc javac będzie generować

StringBuilder # dołączyć

w back-end. Do czasu jdk-9.

Teraz delegatów kodu bajtowego do

StringConcatFactory # makeConcatWithConstants

lub

StringConcatFactory # makeConcat

pośrednictwem invokedynamic instrukcji kodu bajtowego:

aload_0 
    1: aload_2 
    2: aload_1 
    3: invokedynamiC#8, 0 // InvokeDynamiC#0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 
    8: areturn 

Sposób połączenia dwóch łańcuchów jest teraz decyzją środowiska wykonawczego. może to być nadal StringBuilder lub może to być łączenie tablic bajtowych itp. Wszystko, co wiesz, że to się może zmienić, a dostaniesz najszybsze możliwe rozwiązanie.

EDIT

Właśnie debugowany i zobaczył, że istnieje sporo strategii, w jaki sposób dołączyć te ciągi:

private enum Strategy { 
    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}. 
    */ 
    BC_SB, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but trying to estimate the required storage. 
    */ 
    BC_SB_SIZED, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but computing the required storage exactly. 
    */ 
    BC_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also tries to estimate the required storage. 
    */ 
    MH_SB_SIZED, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also estimate the required storage exactly. 
    */ 
    MH_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that constructs its own byte[] array from 
    * the arguments. It computes the required storage exactly. 
    */ 
    MH_INLINE_SIZED_EXACT 
} 

Domyślna istota:

MH_INLINE_SIZED_EXACT