Pytanie Dlaczego konkatenacja ciągu jest szybsza niż String.valueOf w celu konwertowania liczby całkowitej na ciąg?


Mam benchmark:

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
@Measurement(iterations = 40, time = 1, timeUnit = TimeUnit.SECONDS, batchSize = 1000)
public class StringConcatTest {

    private int aInt;

    @Setup
    public void prepare() {
        aInt = 100;
    }

    @Benchmark
    public String emptyStringInt() {
        return "" + aInt;
    }

    @Benchmark
    public String valueOfInt() {
        return String.valueOf(aInt);
    }

}

A oto wynik:

Benchmark                                          Mode  Cnt      Score      Error  Units
StringConcatTest.emptyStringInt                   thrpt   40  66045.741 ± 1306.280  ops/s
StringConcatTest.valueOfInt                       thrpt   40  43947.708 ± 1140.078  ops/s

Pokazuje, że łączenie pustego łańcucha z liczbą całkowitą jest o 30% szybsze niż wywołanie String.value (100). Rozumiem, że "" + 100 konwersji na

new StringBuilder().append(100).toString()

i -XX:+OptimizeStringConcat Zastosowano optymalizację, która sprawia, że ​​jest szybka. Czego nie rozumiem, to dlaczego valueOf sama jest wolniejsza niż konkatenacja. Czy ktoś może wyjaśnić, co dokładnie się dzieje i dlaczego "" + 100 jest szybsze. Jaka magia OptimizeStringConcat robić?


16
2018-02-12 22:17


pochodzenie


"" + 100 jest prawdopodobnie dużo jaśniejsze, aby kompilator rozpoznał jako stałą ... - Louis Wasserman
Jednym z nich jest wywołanie metody. Drugi kompilator może kompilować, ale chce. - Louis Wasserman
@LouisWasserman nie jest skompilowany do stałej. Jest skompilowany do StringBuider() budowa. - Dmitriy Dumanskiy
JIT zwykle nie będzie kopał na mniejszych przebiegach. Jakiego rodzaju rozgrzewkę dałeś benchmarkowi? - Lew Bloch
Zgadzam się, że nie wydaje się, aby tak było w tym przypadku. Niekoniecznie jest to prawda w ogóle i niekoniecznie prawdziwe w przyszłych wersjach kompilatora, ze względu na dyskusję, do której dołączyłem powyżej. - Louis Wasserman


Odpowiedzi:


Jak już wspomniałeś, HotSpot JVM ma -XX:+OptimizeStringConcat optymalizacja, która rozpoznaje wzór StringBuilder i zastępuje go wysoko dostrojonym, ręcznie pisanym grafem IR, podczas gdy String.valueOf() opiera się na ogólnych optymalizacjach kompilatora.

Znalazłem następujące kluczowe różnice, analizując wygenerowany kod zespołu:

  • Zoptymalizowany concat nie wynosi zero char[] tablica utworzona dla ciągu wynikowego, a tablica utworzona przez Integer.toString jest usuwany po przydzieleniu, tak jak każdy inny zwykły obiekt.
  • Zoptymalizowany concat tłumaczy cyfry na znaki przez proste dodanie stałej "0", podczas Integer.getChars używa przeglądanie tabeli z powiązanymi kontrolami zasięgu tablicy itp.

Istnieją inne drobne różnice we wdrażaniu PhaseStringOpts::int_getChars vs. Integer.getChars, ale myślę, że nie są one tak znaczące dla wydajności.


BTW, jeśli weźmiesz większą liczbę (np. 1234567890), różnica w wydajności będzie znikoma z powodu dodatkowa pętla w Integer.getChars który konwertuje dwie cyfry na raz.


12
2018-02-13 01:02