2012-10-12 15 views
13

Korzystam z następującego kodu, aby przetestować powolny blok try. Ku mojemu zaskoczeniu blok try przyspiesza. Czemu?Dlaczego dodanie bloku try przyspiesza działanie programu?

public class Test { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i; 
     long l; 
     Test t = new Test(); 

     l = System.currentTimeMillis(); 
     t.reset(); 
     for (i = 1; i < 100000000; i++) { 
      t.method1(i); 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println("method1 took " + l + " ms, result was " 
       + t.getValue()); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (i = 1; i < 100000000; i++) { 
      try { 
       t.method1(i); 
      } catch (Exception e) { 

      } 
     } 

     l = System.currentTimeMillis() - l; 
     System.out.println("method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

Mój komputer jest wyposażony w 64-bitowy system Windows 7 i 64-bitowy JDK7. Mam następujący wynik:

method1 took 914 ms, result was 2 
method1 with try block took 789 ms, result was 2 

A ja uruchomić kod wiele razy i za każdym razem mam prawie taki sam wynik.

Aktualizacja:

Tutaj jest wynikiem jego przeprowadzenia dziesięć razy na MacBook Pro, Java 6. try-catch sprawia, że ​​metoda szybsza, tak samo jak w systemie Windows.

method1 took 895 ms, result was 2 
method1 with try block took 783 ms, result was 2 
-------------------------------------------------- 
method1 took 943 ms, result was 2 
method1 with try block took 803 ms, result was 2 
-------------------------------------------------- 
method1 took 867 ms, result was 2 
method1 with try block took 745 ms, result was 2 
-------------------------------------------------- 
method1 took 856 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 862 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 859 ms, result was 2 
method1 with try block took 765 ms, result was 2 
-------------------------------------------------- 
method1 took 937 ms, result was 2 
method1 with try block took 767 ms, result was 2 
-------------------------------------------------- 
method1 took 861 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 858 ms, result was 2 
method1 with try block took 744 ms, result was 2 
-------------------------------------------------- 
method1 took 858 ms, result was 2 
method1 with try block took 749 ms, result was 2 
+1

To z zakres pytania, ale powinieneś użyć 'System.nanoTime' do porównania danych. Przeczytaj [System.currentTimeMillis kontra System.nanoTime] (http://stackoverflow.com/q/351565/1065197). –

+2

@RahulAgrawal Zamieniłem kod i otrzymałem ten sam wynik. –

+2

Zrobiłem wiele testów wokół kodu OP i potwierdzam jego odkrycie. –

Odpowiedz

2

Dokonałem kilku eksperymentów.

Na początek całkowicie potwierdzam ustalenie PO. Nawet jeśli usuniesz pierwszą pętlę lub zmienisz wyjątek na zupełnie nieistotny, próba przechwytywania, o ile nie dodasz rozgałęzienia przez ponowne wyrzucenie wyjątku, spowoduje, że kod będzie szybszy. Kod, jeśli jest jeszcze szybszy, jeśli naprawdę musi wychwycić wyjątek (jeśli utworzysz pętlę zaczynającą się od 0 zamiast 1 na przykład).

Moje "wyjaśnienie" jest takie, że JIT są dzikimi maszynami optymalizującymi i że czasami osiągają lepsze wyniki niż inne, w sposób, którego nie można ogólnie zrozumieć bez bardzo konkretnych badań na poziomie JIT. Jest wiele możliwych rzeczy, które mogą się zmienić (na przykład użycie rejestrów).

This is globally what was found in a very similar case with a C# JIT.

W każdym razie, Java jest zoptymalizowany do try-catch. Ponieważ zawsze istnieje możliwość wyjątku, nie dodajesz zbyt wiele rozgałęzień przez dodanie try-catch, więc nie jest zaskoczeniem, że nie znajdziesz drugiej pętli dłużej niż pierwsza.

19

Gdy masz wiele długich pętli uruchomionych w tej samej metodzie, można uruchomić optymalizację całej metody z nieprzewidywalnymi wynikami w drugiej pętli. Jednym ze sposobów na uniknięcie tego jest;

  • otrzymując każda pętla własny sposób
  • przeprowadzenia testów wiele razy, aby sprawdzić wynik jest ponownie powstającemu
  • przeprowadzić test na 2 - 10 s.

Pojawi się pewna odmiana, a czasami wyniki są niejednoznaczne. tj. zmiana jest większa niż różnica.

public class Test { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     Test t = new Test(); 
     for (int i = 0; i < 5; i++) { 
      testWithTryCatch(t); 
      testWithoutTryCatch(t); 
     } 
    } 

    private static void testWithoutTryCatch(Test t) { 
     t.reset(); 
     long l = System.currentTimeMillis(); 
     for (int j = 0; j < 10; j++) 
      for (int i = 1; i <= 100000000; i++) 
       t.method1(i); 

     l = System.currentTimeMillis() - l; 
     System.out.println("without try/catch method1 took " + l + " ms, result was " + t.getValue()); 
    } 

    private static void testWithTryCatch(Test t) { 
     t.reset(); 
     long l = System.currentTimeMillis(); 
     for (int j = 0; j < 10; j++) 
      for (int i = 1; i <= 100000000; i++) 
       try { 
        t.method1(i); 
       } catch (Exception ignored) { 
       } 

     l = System.currentTimeMillis() - l; 
     System.out.println("with try/catch method1 took " + l + " ms, result was " + t.getValue()); 
    } 
} 

drukuje

with try/catch method1 took 9723 ms, result was 2 
without try/catch method1 took 9456 ms, result was 2 
with try/catch method1 took 9672 ms, result was 2 
without try/catch method1 took 9476 ms, result was 2 
with try/catch method1 took 8375 ms, result was 2 
without try/catch method1 took 8233 ms, result was 2 
with try/catch method1 took 8337 ms, result was 2 
without try/catch method1 took 8227 ms, result was 2 
with try/catch method1 took 8163 ms, result was 2 
without try/catch method1 took 8565 ms, result was 2 

Na podstawie tych wyników, może się wydawać, że z try/catch jest nieznacznie wolniejszy, ale nie zawsze.

uruchomić w systemie Windows 7, Xeon E5450 z Java 7 aktualizacji 7.

+0

Na moim komputerze, 'testWithoutTryCatch()' staje się szybszy od trzeciego razu, zawsze. –

+0

Jaką aktualizację Java 7 masz? –

+0

64-bitowa aktualizacja Java 7 7 - "1.7.0_07". –

5

Próbowałem go z Suwmiarka Microbenchmark i naprawdę nie było widać różnicę.

Oto kod:

public class TryCatchBenchmark extends SimpleBenchmark { 

    private int value; 

    public void setUp() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public void timeWithoutTryCatch(int reps) { 
     for (int i = 1; i < reps; i++) { 
      this.method1(i); 
     } 
    } 

    public void timeWithTryCatch(int reps) { 
     for (int i = 1; i < reps; i++) { 
      try { 
       this.method1(i); 
      } catch (Exception ignore) { 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Runner().run(TryCatchBenchmark.class.getName()); 
    } 
} 

A oto wynik:

 
0% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,23 ns; σ=0,03 ns @ 3 trials 
50% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,13 ns; σ=0,03 ns @ 3 trials 

     benchmark ns linear runtime 
WithoutTryCatch 8,23 ============================== 
    WithTryCatch 8,13 ============================= 

Gdybym zamienić kolejność funkcji (aby je uruchomić w odwrotnej kolejności) wynik jest:

 
0% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,21 ns; σ=0,05 ns @ 3 trials 
50% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,14 ns; σ=0,03 ns @ 3 trials 

     benchmark ns linear runtime 
    WithTryCatch 8,21 ============================== 
WithoutTryCatch 8,14 ============================= 

Powiedziałbym, że są one w zasadzie takie same.

+0

+1 Używanie mikroprocesora zacisku. – Ryan

1

Aby uniknąć jakichkolwiek ukrytych lub optymalizację pamięci podręcznej, które mogą być wykonywane przez JVM i systemu operacyjnego, po raz pierwszy opracowano dwa programy java nasion, TryBlock i NoTryBlock, gdzie ich różnicą jest użycie bloku try, czy nie. Te dwa programy źródłowe będą używane do generowania różnych programów, aby uniemożliwić JVM lub systemowi OS wykonanie ukrytej optymalizacji. W każdym teście zostanie wygenerowany i skompilowany nowy program java, a ja powtórzyłem test 10 razy.

Na podstawie mojego eksperymentu, praca bez bloku try trwa średnio 9779,3 mil podczas pracy z blokiem try zajmuje 9775.9 ms: różnica 3,4 ms (lub 0,035%) w ich średnim czasie działania, który można postrzegać jako szum. Oznacza to, że użycie pustego bloku try (przez pustkę, mam na myśli inny niż wyjątek typu "zero", nie ma możliwych wyjątków) lub nie ma wpływu na czas działania.

Test działa na tym samym komputerze z systemem Linux (procesor 2392 MHz) oraz w wersji java "1.6.0_24".

Poniżej jest mój skrypt do generowania programów badawczych na podstawie programów nasiennych:

for i in `seq 1 10`; do 
    echo "NoTryBlock$i" 
    cp NoTryBlock.java NoTryBlock$i.java 
    find . -name "NoTryBlock$i.java" -print | xargs sed -i "s/NoTryBlock/NoTryBlock$i/g"; 
    javac NoTryBlock$i.java; 
    java NoTryBlock$i 
    rm NoTryBlock$i.* -f; 
done 

for i in `seq 1 10`; do 
    echo "TryBlock$i" 
    cp TryBlock.java TryBlock$i.java 
    find . -name "TryBlock$i.java" -print | xargs sed -i "s/TryBlock/TryBlock$i/g"; 
    javac TryBlock$i.java; 
    java TryBlock$i 
    rm TryBlock$i.* -f; 
done 

Oto programy nasienne, najpierw jest NoTryBlock.java

import java.util.*; 
import java.lang.*; 

public class NoTryBlock { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i, j; 
     long l; 
     NoTryBlock t = new NoTryBlock(); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (j = 1; j < 10; ++j) { 
      for (i = 1; i < 100000000; i++) { 
       t.method1(i); 
      } 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println(
      "method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

drugi jest TryBlock.java, który wykorzystuje blok próbny w wywołaniu funkcji metody:

import java.util.*; 
import java.lang.*; 

public class TryBlock { 
    int value; 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    // Calculates without exception 
    public void method1(int i) { 
     value = ((value + i)/i) << 1; 
     // Will never be true 
     if ((i & 0xFFFFFFF) == 1000000000) { 
      System.out.println("You'll never see this!"); 
     } 
    } 

    public static void main(String[] args) { 
     int i, j; 
     long l; 
     TryBlock t = new TryBlock(); 

     // using a try block 
     l = System.currentTimeMillis(); 
     t.reset(); 
     for (j = 1; j < 10; ++j) { 
      for (i = 1; i < 100000000; i++) { 
      try { 
       t.method1(i); 
      } catch (Exception e) { 
      } 
      } 
     } 
     l = System.currentTimeMillis() - l; 
     System.out.println(
      "method1 with try block took " + l + " ms, result was " 
       + t.getValue()); 
    } 
} 

Poniżej jest diff z moich dwóch programów nasiennych, można zobaczyć oprócz nazwy klasy, blok try to ich jedyna różnica:

$ diff TryBlock.java NoTryBlock.java 
4c4 
<  public class TryBlock { 
--- 
>  public class NoTryBlock { 
27c27 
<    TryBlock t = new TryBlock(); 
--- 
>    NoTryBlock t = new NoTryBlock(); 
34d33 
<     try { 
36,37d34 
<     } catch (Exception e) { 
<     } 
42c39 
<     "method1 with try block took " + l + " ms, result was " 
--- 
>     "method1 without try block took " + l + " ms, result was " 

Poniżej jest wyjście:

method1 without try block took,9732,ms, result was 2 
method1 without try block took,9756,ms, result was 2 
method1 without try block took,9845,ms, result was 2 
method1 without try block took,9794,ms, result was 2 
method1 without try block took,9758,ms, result was 2 
method1 without try block took,9733,ms, result was 2 
method1 without try block took,9763,ms, result was 2 
method1 without try block took,9893,ms, result was 2 
method1 without try block took,9761,ms, result was 2 
method1 without try block took,9758,ms, result was 2 

method1 with try block took,9776,ms, result was 2 
method1 with try block took,9751,ms, result was 2 
method1 with try block took,9767,ms, result was 2 
method1 with try block took,9726,ms, result was 2 
method1 with try block took,9779,ms, result was 2 
method1 with try block took,9797,ms, result was 2 
method1 with try block took,9845,ms, result was 2 
method1 with try block took,9784,ms, result was 2 
method1 with try block took,9787,ms, result was 2 
method1 with try block took,9747,ms, result was 2 
Powiązane problemy