замедление try-catch для статических методов в Java - PullRequest
2 голосов
/ 30 сентября 2011

Я проверял это обсуждение: Насколько медленны исключения Java? и во время экспериментов обнаружил, что если я запускаю статические методы вместо методов экземпляра, то обычный путь на самом деле занимает больше времени, чем try-catchдорожка.

Что я делаю: создаю статический метод foo() без операции, создаю статический метод method1(), который обычно вызывает foo() 100000000 раз, и другой статический метод method2(), который вызывает foo() 100000000 раз в блоке try-catch.Я вижу, что method2 на самом деле занимает меньше времени, чем method1.

Есть мысли?

public class ExceptionsStatic
{
    public static void main(String... args)
    {               
        withNormal();
        withTry();      
    }

    static void foo()
    {

    }

    static void foo2() throws Exception
    {

    }

    static void withTry()
    {
        long t1 = System.currentTimeMillis();

        for(int i = 0; i < 100000000; i++)
        {
            try
            {
                foo2();
            }
            catch(Exception e)
            {

            }
        }

        long t2 = System.currentTimeMillis();

        System.out.println("try time taken " + (t2 - t1));
    }

    static void withNormal()
    {
        long t1 = System.currentTimeMillis();

        for(int i = 0; i < 100000000; i++)
        {
            foo();
        }

        long t2 = System.currentTimeMillis();

        System.out.println("normal time taken " + (t2 - t1));
    }
}

Ответы [ 3 ]

2 голосов
/ 30 сентября 2011

Я попытался воссоздать ваш тестовый код и затем запустить его через javap.Они даются в конце, чтобы вам не приходилось пролистывать большой текстовый блок.

Обратите внимание, что когда виртуальная машина не выполняет абсолютно никакой оптимизации, байт-код выполняется согласно дампу javap.ниже.Таким образом, при условии отсутствия других внешних факторов, выполнение method2() должно всегда занимать больше времени, поскольку включает дополнительную инструкцию (line 11: goto 15).

Конечно, как упоминает Иоахим ниже: 'байт-код очень мало говорит о производительности '.

Для профилирования и включения / отключения оптимизации JVM доступно множество флагов.Посмотрите онлайн.Для 1.4.2 я нашел эту ссылку , которая также может работать с более новыми JRE.

Отредактировано для добавления: В поддерживаемых ВМ вы можете включить вывод трассировки JIT, используя следующий флаг ВМ -XX:-PrintCompilation.


вывод javap:

Ryan-Schippers-MacBook-Pro-2:Miscellaneous work$ javap -c -classpath ./src SlowTryCatch
Compiled from "SlowTryCatch.java"
public class SlowTryCatch extends java.lang.Object{
public SlowTryCatch();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   return

public static void foo();
  Code:
   0:   return

public static void method1();
  Code:
   0:   iconst_0
   1:   istore_0
   2:   iload_0
   3:   ldc #2; //int 100000000
   5:   if_icmpge   17
   8:   invokestatic    #3; //Method foo:()V
   11:  iinc    0, 1
   14:  goto    2
   17:  return

public static void method2();
  Code:
   0:   iconst_0
   1:   istore_0
   2:   iload_0
   3:   ldc #2; //int 100000000
   5:   if_icmpge   21
   8:   invokestatic    #3; //Method foo:()V
   11:  goto    15
   14:  astore_1
   15:  iinc    0, 1
   18:  goto    2
   21:  return
  Exception table:
   from   to  target type
     8    11    14   Class java/lang/Exception


}
0 голосов
/ 01 октября 2011

Вот микро тест, который сосет меньше.Используйте 1 или 2 в качестве параметров программы и -XX:+PrintCompilation -verbose:class -verbose:gc в качестве параметров JVM.

public class TryBlockBenchmark {
    private static final int MEASUREMENTS = 100;
    private static int dummy = 0;

    public static void main(String[] args) {
        boolean tryBlock = args[0].equals("1");
        System.out.println(tryBlock ? "try block" : "no try block");

        for (int i = 0; i < MEASUREMENTS; i++) {

            long start = System.currentTimeMillis();
            if (tryBlock) {
                benchmarkTryBlock();
            } else {
                benchmarkNoTryBlock();
            }
            long end = System.currentTimeMillis();

            System.out.println((end - start) + " ms");
        }

        System.out.println("(" + dummy + ")");
    }

    private static void benchmarkTryBlock() {
        for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
            try {
                staticMethod();
            } catch (Exception e) {
            }
        }
    }

    private static void benchmarkNoTryBlock() {
        for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
            staticMethod();
        }
    }

    private static void staticMethod() {
        dummy++;
    }
}

С 64-битной виртуальной машиной HotSpot Java 1.6.0_24 на C2Q6600 @ 3GHz после первой парыизмерения, время для обеих версий стабилизируется на 266 мс (+/- 1 мс).Время то же самое, когда staticMethod() вводится вручную, чего и следует ожидать от HotSpot.Удаление строки dummy++ сокращает время до 0 мс, когда HotSpot оптимизирует его.

Я также протестировал 32-битную версию Java 1.6.0_24, и с ней виртуальная машина сервера HotSpot показала те же результаты, ноКлиентская виртуальная машина HotSpot имела обе версии, выдающие результаты около 8660 мс (+/- 20 мс).

Таким образом, мы можем сделать вывод, что виртуальная машина сервера имеет лучшую оптимизацию, чем виртуальная машина клиента, и что попытка перехвата ничего не делаетлибо оптимизирован HotSpot, либо не влияет на производительность.Чтобы выяснить, что это такое, напечатайте код сборки , созданный HotSpot.

В целом, измерять вещи, которые ничего не делают, довольно бессмысленно.

0 голосов
/ 30 сентября 2011

Я немного изменил ваш код, чтобы оптимизатор не удалял код. Запустив его несколько раз в Sun / Oracle JVM, я обнаружил следующее:

  • Время выполнения не является детерминированным. Это обычно в JVM HotSpot, особенно в многоядерных системах
  • Различия между withNormal() и withTry минимальны. Этого и следовало ожидать, так как фактическое исключение не выдается, и нет блоков finally.
  • Версия, которая запускается первой, обычно медленнее. Возможно, это связано с «разогревом» компилятора HotSpot, но я не специалист по внутренним компонентам HotSpot

Подводя итог, я не ожидал бы существенной разницы между кодом, использующим исключения или нет, при работе в Sun / Oracle JVM это, скорее всего, шум от HotSpot.

UPDATE

Я запустил его с флагами -server и -client и, кроме того, что выполнение на моей машине на порядок быстрее, применимы вышеприведенные наблюдения.

Модифицированный код ниже:

public class ExceptionsStatic    {

public static void main(String... args)
{
    withNormal();
    withTry();
}

static int fooVar;
static void foo()
{
    fooVar++;
}

static int foo2Var;
static void foo2() throws Exception
{
    foo2Var++;
}

static void withTry()
{
    long t1 = System.currentTimeMillis();

    foo2Var = 0;
    for(int i = 0; i < 100000000; i++)
    {
        try
        {
            foo2();
        }
        catch(Exception e)
        {

        }
    }

    long t2 = System.currentTimeMillis();

    System.out.println("try time taken " + (t2 - t1) + "; " + foo2Var);
}

static void withNormal()
{
    long t1 = System.currentTimeMillis();

    fooVar = 0;
    for(int i = 0; i < 100000000; i++)
    {
        foo();
    }

    long t2 = System.currentTimeMillis();

    System.out.println("normal time taken " + (t2 - t1) + "; " + fooVar);
}
...