2013-03-23 12 views
14

Próbowałem utworzyć szybszą wersję metody String.equals() i zacząłem od jej skopiowania. Rezultat, który znalazłem, był dość zagmatwany. Kiedy uruchomiłem kopiowaną wersję, ustawiłem ją na czas i porównałem z wersją JVM, wersja JVM była szybsza. Różnica wahała się od 6x do 34x szybciej! Mówiąc krótko, im dłuższy ciąg, tym większa różnica.Dlaczego metoda String.equals() jest szybsza od niej samej?

boolean equals(final char a[], final char b[]) { 
    int n = a.length; 
    int i = 0; 

    while (n-- != 0) { 
     if (a[i] != b[i]) return false; 
     i++; 
    } 
    return true; 
} 

public static void main() throws Exception { 
    String a = "blah balh balh"; 
    String b = "blah balh balb"; 

    long me = 0, jvm = 0; 

    Field value = String.class.getDeclaredField("value"); 
    value.setAccessible(true); 

    final char lhs[] = (char[]) value.get(a); 
    final char rhs[] = (char[]) value.get(b); 
    for (int i = 0; i < 100; i++) { 
     long t = System.nanoTime(); 
     equals(lhs, rhs); 
     t = System.nanoTime() - t; 
     me += t; 
    } 

    for (int i = 0; i < 100; i++) { 
     long t = System.nanoTime(); 
     a.equals(b); 
     t = System.nanoTime() - t; 
     jvm += t; 
    } 

    System.out.println("me = " + me); 
    System.out.println("jvm = " + jvm); 
} 

wyjściowa:

me = 258931 
jvm = 14991 

Metoda equals() Napisałem to kopia wklejony wersja znalezionego w String.equals() metody. Dlaczego wersja JVM jest szybsza niż w wersji wklejonej do kopiowania? Czy to nie jest faktycznie to samo?

Czy ktoś mógłby wyjaśnić, dlaczego widzę tak widoczne różnice?

PS: Jeśli chcesz zobaczyć duże różnice, możesz utworzyć długie (naprawdę, bardzo długie) łańcuchy z jedną postacią, różniącą się na końcu.

+1

Po prostu zgadnij, ale myślę, że może to być związane z optymalizacją środowiska wykonawczego maszyny JVM. Natywna wersja jest prawdopodobnie używana wewnętrznie. Metody, które są często używane, są z większym prawdopodobieństwem optymalizowane przez maszynę wirtualną JVM. – Philipp

+1

Myślę, że 'jvm' optymalizuje' String.equals' do równoważnej instrukcji asemblera na podstawie jego nazwy, a nie kodu. I prawdopodobnie również to podkreśla. Po skopiowaniu kodu utracone zostaną optymalizacje. – doublep

+1

@Phillip: Też to zgadłem. Jeśli tak, to JVM traktuje swoje własne klasy specjalnie! –

Odpowiedz

13

Dlaczego wersja JVM jest szybsza niż w wersji wklejonej do kopiowania. Czy to nie jest faktycznie to samo?

Co zaskakujące, nie jest.

Porównanie ciągów jest tak wszechobecną operacją, że prawie na pewno jest tak, że twój JIT compiler ma nieodłączne dla String.equals(). Oznacza to, że kompilator wie, jak wygenerować specjalnie spreparowany kod maszynowy do porównywania łańcuchów. Odbywa się to w sposób przejrzysty dla ciebie, programisty, kiedy używasz String.equals().

To wyjaśnia, dlaczego String.equals() jest o wiele szybszy od metody, nawet jeśli powierzchownie wydają się identyczne.

Szybkie wyszukiwanie znajduje kilka raportów o błędach, które wspominają o takich nieodłącznych elementach w HotSpot. Na przykład 7041100 : The load in String.equals intrinsic executed before null check.

Odpowiednie źródło HotSpot można znaleźć here. Funkcje te zostały wydane:

848 Node* LibraryCallKit::make_string_method_node(int opcode, Node* str1, Node* cnt1, Node* str2, Node* cnt2) { 

i

943 bool LibraryCallKit::inline_string_equals() { 
+0

Nawet jeśli JIT nie ma właściwości wewnętrznych, JVM może. Możliwe jest zastąpienie metody JDK natywną metodą "under the covers", i nie założyłbym się, że nie jest to zrobione. –

+0

@HotLicks: Czy to oznacza, że ​​mogę napisać JNI pod maską, żeby używał zestawu instrukcji SIMD do wykonywania porównań, które mogłyby pokonać wewnętrzne lub JVM/JDK tubylców? –

+0

@Eshan: Możesz spróbować. Oczywiście nie ma gwarancji, że ci się uda. – NPE

1

Hotspot umożliwia programistom zapewnienie natywną wdrożenie metody oprócz realizacji Java. Kod Java jest zamieniany w środowisku wykonawczym i zastępowany przez zoptymalizowaną wersję. Nazywa się to wewnętrznym. Kilkaset metod z klas podstawowych jest zoptymalizowanych przez elementy wewnętrzne.

Patrząc na kod źródłowy OpenJDK można uzyskać see the x86_64 implementation of String.equals. Możesz również zajrzeć do vmSymbols, aby uzyskać listę wszystkich instrinetyk (poszukiwanie do_intrinsic)

Powiązane problemy