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 exponents. 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ę 19cases 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 zcases 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ęcejcases, 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-27cases (nawet jeśli te fałszywe przypadki nigdy nie zostaną trafione podczas działania kodu). (Jeszcze raz,cases 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ęcejcases 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.

questionAnswers(4)

yourAnswerToTheQuestion