JIT - микрооптимизация - если исключить оператор - PullRequest
0 голосов
/ 13 ноября 2018

Предположим, у нас есть следующий код:

public static void check() {   
    if (Config.initialized) {
           ...
    }
}

Config.initialized имеет значение false в начале и изменяется на true только в какой-то момент после того, как метод уже скомпилирован в JIT.Значение никогда не возвращается к ложному.

Я "знаю", что существует много очень сложных оптимизаций (развертывание цикла, предсказание ветвления, встраивание, анализ выхода и т. Д.), И хотя я далек от понимания всех их в деталях, я 'Сейчас я в основном заинтересован в следующем:

  1. Имеет ли JIT-компилятор способ определить, что if всегда будет истинным после определенного момента времени, чтобы можно было пропустить проверкуполностью?Под полностью я действительно подразумеваю отсутствие доступа к переменной, никакой проверки состояния / jne и т. Д.) ненужная проверка из образца (и я бы не знал, как это могло), могу ли я что-нибудь сделать, чтобы поддержать это?Моя единственная идея - повторно преобразовать класс и удалить ненужный код из байтового кода после того, как произошло событие инициализации.

Я знаю, что это полная микрооптимизация и, вероятно, ее трудно измерить, даже используя такие инструменты, как JMH, но я все же хотел бы знать и понимать.

Последнее, ноне менее важно:

Верно ли мое понимание того, что если вышеупомянутый метод где-то указывается, что все эти методы будут перекомпилированы (при условии, что они горячие), если что-то изменится так, что потребуется перекомпилировать метод check?

Если я правильно понимаю результаты моего теста JitWatch, ответ на поставленные выше вопросы должен быть:

  1. Нет, ни за что.Там всегда будет проверка состояния.
  2. Действительно только путем преобразования
  3. Да

1 Ответ

0 голосов
/ 13 ноября 2018
  1. Имеет ли JIT-компилятор способ определить, что if всегда будет истинным после определенной точки

Да, если поле равно static finalи его класс держателей был инициализирован ко времени запуска JIT-компилятора. Очевидно, это не применимо в вашем случае, так как Config.initialized нельзя сделать static final.

могу ли я что-нибудь сделать, чтобы поддержать это?

java.lang.invoke.MutableCallSite на помощь.

Этот класс предназначен специально дляделать то, что вы просите.Его метод setTarget поддерживает повторное связывание сайта вызова во время выполнения.Под капотом это вызывает деоптимизацию текущего скомпилированного метода с возможностью позже перекомпилировать его с новой целью.

A MethodHandle для вызова цели MutableCallSite можно получить с помощью dynamicInvoker метод.Обратите внимание, что MethodHandle должно быть static final, чтобы разрешить встраивание.

если вышеупомянутый метод где-то указывает, что все эти методы будут перекомпилированы

Да.

Вот тест, демонстрирующий, что метод mutableCallSite такой же быстрыйкак alwaysFalse в начале, а также так же быстро, как alwaysTrue после переключения тумблера.Я также включил переключатель статического поля для сравнения, как предложил @Holger.

package bench;

import org.openjdk.jmh.annotations.*;
import java.lang.invoke.*;
import java.util.concurrent.*;

@State(Scope.Benchmark)
public class Toggle {
    static boolean toggleField = false;

    static final MutableCallSite toggleCallSite =
            new MutableCallSite(MethodHandles.constant(boolean.class, false));

    static final MethodHandle toggleMH = toggleCallSite.dynamicInvoker();

    public void switchToggle() {
        toggleField = true;
        toggleCallSite.setTarget(MethodHandles.constant(boolean.class, true));
        MutableCallSite.syncAll(new MutableCallSite[]{toggleCallSite});
        System.out.print("*** Toggle switched *** ");
    }

    @Setup
    public void init() {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.schedule(this::switchToggle, 10100, TimeUnit.MILLISECONDS);
        executor.shutdown();
    }

    @Benchmark
    public int alwaysFalse() {
        return 0;
    }

    @Benchmark
    public int alwaysTrue() {
        return ThreadLocalRandom.current().nextInt();
    }

    @Benchmark
    public int field() {
        if (toggleField) {
            return ThreadLocalRandom.current().nextInt();
        } else {
            return 0;
        }
    }

    @Benchmark
    public int mutableCallSite() throws Throwable {
        if ((boolean) toggleMH.invokeExact()) {
            return ThreadLocalRandom.current().nextInt();
        } else {
            return 0;
        }
    }
}

Запустив тест с 5 итерациями прогрева и 10 итерациями измерения, я получаю следующие результаты:

# JMH version: 1.20
# VM version: JDK 1.8.0_192, VM 25.192-b12

# Benchmark: bench.Toggle.alwaysFalse

# Run progress: 0,00% complete, ETA 00:01:00
# Fork: 1 of 1
# Warmup Iteration   1: 3,875 ns/op
# Warmup Iteration   2: 3,369 ns/op
# Warmup Iteration   3: 2,699 ns/op
# Warmup Iteration   4: 2,696 ns/op
# Warmup Iteration   5: 2,703 ns/op
Iteration   1: 2,697 ns/op
Iteration   2: 2,696 ns/op
Iteration   3: 2,696 ns/op
Iteration   4: 2,706 ns/op
Iteration   5: *** Toggle switched *** 2,698 ns/op
Iteration   6: 2,698 ns/op
Iteration   7: 2,692 ns/op
Iteration   8: 2,707 ns/op
Iteration   9: 2,712 ns/op
Iteration  10: 2,702 ns/op


# Benchmark: bench.Toggle.alwaysTrue

# Run progress: 25,00% complete, ETA 00:00:48
# Fork: 1 of 1
# Warmup Iteration   1: 5,159 ns/op
# Warmup Iteration   2: 5,198 ns/op
# Warmup Iteration   3: 4,314 ns/op
# Warmup Iteration   4: 4,321 ns/op
# Warmup Iteration   5: 4,306 ns/op
Iteration   1: 4,306 ns/op
Iteration   2: 4,310 ns/op
Iteration   3: 4,297 ns/op
Iteration   4: 4,324 ns/op
Iteration   5: *** Toggle switched *** 4,356 ns/op
Iteration   6: 4,300 ns/op
Iteration   7: 4,310 ns/op
Iteration   8: 4,290 ns/op
Iteration   9: 4,297 ns/op
Iteration  10: 4,294 ns/op


# Benchmark: bench.Toggle.field

# Run progress: 50,00% complete, ETA 00:00:32
# Fork: 1 of 1
# Warmup Iteration   1: 3,596 ns/op
# Warmup Iteration   2: 3,429 ns/op
# Warmup Iteration   3: 2,973 ns/op
# Warmup Iteration   4: 2,937 ns/op
# Warmup Iteration   5: 2,934 ns/op
Iteration   1: 2,927 ns/op
Iteration   2: 2,928 ns/op
Iteration   3: 2,932 ns/op
Iteration   4: 2,929 ns/op
Iteration   5: *** Toggle switched *** 3,002 ns/op
Iteration   6: 4,887 ns/op
Iteration   7: 4,866 ns/op
Iteration   8: 4,877 ns/op
Iteration   9: 4,867 ns/op
Iteration  10: 4,877 ns/op


# Benchmark: bench.Toggle.mutableCallSite

# Run progress: 75,00% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration   1: 3,474 ns/op
# Warmup Iteration   2: 3,332 ns/op
# Warmup Iteration   3: 2,750 ns/op
# Warmup Iteration   4: 2,701 ns/op
# Warmup Iteration   5: 2,701 ns/op
Iteration   1: 2,697 ns/op
Iteration   2: 2,696 ns/op
Iteration   3: 2,699 ns/op
Iteration   4: 2,706 ns/op
Iteration   5: *** Toggle switched *** 2,771 ns/op
Iteration   6: 4,310 ns/op
Iteration   7: 4,306 ns/op
Iteration   8: 4,312 ns/op
Iteration   9: 4,317 ns/op
Iteration  10: 4,301 ns/op
...