Pull to refresh

Пример оптимизации в JVM

Reading time2 min
Views584
Набрел на интересный пример оптимизации при динамической компиляции
public class StupidMathTest {
  public interface Operator {
    public double operate(double d);
  }

  public static class SimpleAdder implements Operator {
    public double operate(double d) {
      return d + 1.0;
    }
  }

  public static class DoubleAdder implements Operator {
    public double operate(double d) {
      return d + 0.5 + 0.5;
    }
  }

  public static class RoundaboutAdder implements Operator {
    public double operate(double d) {
      return d + 2.0 - 1.0;
    }
  }

  public static void runABunch(Operator op) {
    long start = System.currentTimeMillis();
    double d = 0.0;
    for (int i = 0; i < 5000000; i++)
      d = op.operate(d);
    long end = System.currentTimeMillis();
    System.out.println("Time: " + (end-start) + "  ignore:" + d);
  }

  public static void main(String[] args) {
    Operator ra = new RoundaboutAdder();
    runABunch(ra);
    Operator sa = new SimpleAdder();
    Operator da = new DoubleAdder();
    runABunch(sa);
    runABunch(da);
  }
}

Method        Runtime
SimpleAdder     88ms
DoubleAdder     76ms
RoundaboutAdder 14ms


* This source code was highlighted with Source Code Highlighter.


Похоже, что намного быстрее добавить 1 к double, добавив 2 и отняв 1, чем просто добавив 1 сразу же. И немного быстрее добавить 0.5 дважды, чем добавить 1. Возможно ли такое? (Ответ: нет.)

Что здесь произошло? После цикла тренировки RoundaboutAdder и runABunch() компилируются, и компилятор выполняет мономорфное преобразование вызова (преобразование вызова виртуального метода в прямой вызов метода называется мономорфным преобразованием вызова) с Operator и RoundaboutAdder, так что первый проход выполняется довольно быстро. Во втором проходе (SimpleAdder) компилятор должен выполнить деоптимизацию и откат к диспетчеризации виртуального метода, так что второй проход выполняется медленнее благодаря неспособности оптимизировать (удалить) вызов виртуальной функции, время тратится на перекомпиляцию. На третьем проходе (DoubleAdder) выполняется меньше перекомпиляции, поэтому он работает быстрее.

Итак, какой мы можем сделать вывод из этого «теста производительности»? Практически никакого, за исключением того, что тестирование производительности динамически компилируемых языков программирования является намного более коварным процессом, чем вы могли бы представить.

Оригинал статьи
www.ibm.com/developerworks/ru/library/j-jtp12214/index.html
Tags:
Hubs:
+11
Comments6

Articles