Подтвердить что ты не робот

Почему добавление блока try делает программу быстрее?

Я использую следующий код, чтобы проверить, насколько медленным является блок try. К моему удивлению, блок try делает это быстрее. Почему?

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());
    }
}

В моей машине работают 64-разрядные Windows 7 и 64-разрядные JDK7. Я получил следующий результат:

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

И я запускаю код много раз, и каждый раз, когда у меня получается почти тот же результат.

Update:

Вот результат десятикратного запуска теста на MacBook Pro, Java 6. Try-catch делает метод быстрее, так же как и в 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
4b9b3361

Ответ 1

Если у вас несколько длинных циклов работы в одном и том же методе, можно запустить оптимизацию всего метода с непредсказуемыми результатами во втором цикле. Один из способов избежать этого:

  • чтобы дать каждому циклу свой собственный метод
  • запускать тесты несколько раз, чтобы проверить, что результат повторный.
  • запустите тест на 2 - 10 секунд.

Вы увидите некоторые вариации, и иногда результаты неубедительны. то есть изменение выше разницы.

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());
    }
}

печатает

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

Из этих результатов может показаться, что при попытке/улове немного медленнее, но не всегда.

Запуск в Windows 7, Xeon E5450 с обновлением 7 Java.

Ответ 2

Я попробовал его с помощью Caliper Microbenchmark, и я действительно не видел разницы.

Здесь код:

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());
    }
}

И вот результат:

 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 =============================

Если я поменяю порядок функций (чтобы заставить их работать в обратном порядке), результат:

 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 =============================

Я бы сказал, что они в основном одинаковы.

Ответ 3

Я сделал несколько экспериментов.

Для начала я полностью подтверждаю нахождение OP. Даже если вы удаляете первый цикл или изменяете исключение на какой-то совершенно несущественный, try catch, если вы не добавляете ветвление путем повторного создания исключения, делает код быстрее. Код, если еще быстрее, если он действительно должен поймать исключение (если вы начинаете цикл с 0 вместо 1, например).

Мое "объяснение" заключается в том, что JIT - это дикие машины для оптимизации, и иногда они работают лучше, чем когда-либо, так, как вы обычно не понимаете, без особого изучения на уровне JIT. Есть много возможных вещей, которые могут измениться (например, использование регистров).

Это глобально то, что было найдено в очень похожем случае с С# JIT.

В любом случае Java оптимизирован для try-catch. Поскольку всегда существует вероятность исключения, вы действительно не добавляете много ветвлений, добавляя try-catch, поэтому неудивительно, что второй цикл больше, чем первый.

Ответ 4

Чтобы избежать какой-либо скрытой оптимизации или кеша, которые могли бы быть реализованы JVM и ОС, я сначала разработал две семестровые Java-программы, TryBlock и NoTryBlock, где их разница использует блок try или нет. Эти две программы семян будут использоваться для создания различных программ, чтобы запретить JVM или ОС для скрытой оптимизации. В каждом тесте будет создана и скомпилирована новая java-программа, и я повторил тест 10 раз.

В соответствии с моим экспериментом работа без блока try занимает в среднем 9779,3 мс, а работа с блоком try занимает 9775,9 мс: разница в среднем 3,4 мс (или 0,035%) в среднем времени работы, что можно рассматривать как шум. Это указывает на то, что использование блока void try (по void, я имею в виду, кроме исключения из null-указателя, не существует возможных исключений) или нет, похоже, не влияет на время выполнения.

Тест выполняется на той же машине linux (cpu 2392MHz) и в версии java "1.6.0_24".

Ниже приведен мой script для создания тестовых программ на основе семенных программ:

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

Вот программы семян, сначала это 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());
    }
}

второй - это TryBlock.java, который использует метод try на вызове функции метода:

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());
    }
}

Ниже приведена разница между двумя моими программами семени, и вы можете видеть, кроме имени класса, блок try является их единственной разницей:

$ 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 "

Ниже представлен результат:

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