Dlaczego przełączanie Java na sąsiednie int wydaje się działać szybciej z dodanymi przypadkami?
Pracuję nad jakimś kodem Java, który musi być zoptymalizowany, ponieważ będzie działał w gorących funkcjach, które są wywoływane w wielu punktach mojej logiki programu głównego. Część tego kodu polega na mnożeniudouble
zmienne wg10
podniesiony do dowolnego nieujemnegoint
exponent
s. Jeden szybki sposób (edycja: ale nie najszybszy możliwy, patrz aktualizacja 2 poniżej), aby uzyskać wartość pomnożonąswitch
naexponent
:
double multiplyByPowerOfTen(final double d, final int exponent) {
switch (exponent) {
case 0:
return d;
case 1:
return d*10;
case 2:
return d*100;
// ... same pattern
case 9:
return d*1000000000;
case 10:
return d*10000000000L;
// ... same pattern with long literals
case 18:
return d*1000000000000000000L;
default:
throw new ParseException("Unhandled power of ten " + power, 0);
}
}
Skomentowane elipsy powyżej wskazują, żecase
int
Stałe wzrastają o 1, więc jest ich naprawdę 19case
s w powyższym fragmencie kodu. Ponieważ nie byłam pewna, czy rzeczywiście potrzebowałabym wszystkich mocy 10 calicase
sprawozdania10
przez18
, Uruchomiłem kilka mikrobenchów porównujących czas wykonania 10 milionów operacji z tymswitch
instrukcja a aswitch
tylko zcase
s 0
przez9
(za pomocąexponent
ograniczony do 9 lub mniej, aby uniknąć zerwaniaswitch
). Mam dość zaskakujący (przynajmniej dla mnie!) Wynik, że im dłużejswitch
z więcejcase
instrukcje rzeczywiście działały szybciej.
Na skowronku próbowałem dodawać jeszcze więcejcase
s, które właśnie zwróciły wartości atrapy, i odkryły, że mogłem uruchomić przełącznik jeszcze szybciej z deklaracją 22-27case
s (nawet jeśli te fałszywe przypadki nigdy nie zostaną trafione podczas działania kodu). (Jeszcze raz,case
s zostały dodane w sposób ciągły, zwiększając wartość poprzedniejcase
stały przez1
.) Te różnice czasu wykonania nie są bardzo znaczące: dla losowegoexponent
pomiędzy0
i10
, manekin wyściełanyswitch
instrukcja kończy 10 milionów wykonań w 1.49 sek. w porównaniu do 1.54 sek. dla wersji bez klawiatury, co daje ogromne oszczędności 5ns na wykonanie. A więc nie tego rodzaju rzeczy, które sprawiają, że obsesja na punkcie wyrzuceniaswitch
oświadczenie warte wysiłku z punktu widzenia optymalizacji. Ale wciąż uważam to za dziwne i sprzeczne z intuicją, że aswitch
nie staje się wolniejszy (a może najlepiej utrzymywać stałyO (1) czas), aby wykonać więcejcase
s są do niego dodawane.
Są to wyniki, które otrzymałem z pracy z różnymi limitami losowo generowanychexponent
wartości. Nie uwzględniłem wyników aż do1
dlaexponent
limit, ale ogólny kształt krzywej pozostaje taki sam, z grzbietem wokół znaku przypadku 12-17 i doliną pomiędzy 18-28. Wszystkie testy zostały uruchomione w JUnitBenchmarks przy użyciu współużytkowanych kontenerów dla losowych wartości, aby zapewnić identyczne wejścia testowe. Przeprowadziłem również testy w kolejności od najdłuższejswitch
instrukcja do najkrótszej i odwrotnie, aby spróbować wyeliminować możliwość problemów testowych związanych z zamawianiem. Umieściłem mój kod testowy na repozytorium github, jeśli ktoś chce spróbować odtworzyć te wyniki.
Więc co tu się dzieje? Jakieś kaprysy mojej architektury lub konstrukcji mikro-benchmarków? Czy jest to Javaswitch
naprawdę trochę szybciej wykonać w18
do28
case
zasięg niż z11
aż do17
?
test github repo „eksperyment przełączania”
AKTUALIZACJA: Wyczyściłem trochę bibliotekę benchmarkingową i dodałem plik tekstowy w / results z pewnym wyjściem w szerszym zakresie możliwychexponent
wartości. Dodałem również opcję w kodzie testowania, aby nie rzucaćException
zdefault
, ale to nie wydaje się wpływać na wyniki.
AKTUALIZACJA 2: Znalazłem całkiem niezłą dyskusję na ten temat w 2009 r. Na forum xkcd tutaj:http://forums.xkcd.com/viewtopic.php?f=11&t=33524. Dyskusja OP na temat wykorzystaniaArray.binarySearch()
dał mi pomysł na prostą implementację wzorca wykładniczego na podstawie tablicy. Nie ma potrzeby wyszukiwania binarnego, ponieważ wiem, jakie są wpisy warray
są Wydaje się, że działa około 3 razy szybciej niż przy użyciuswitch
, oczywiście kosztem pewnego przepływu sterowaniaswitch
zapewnia. Ten kod został również dodany do repo github.