Как заставить Scala или Java JVM удалить ветвление, если во время выполнения условие одинаковое?

Например, у меня есть такое выражение

if (DEBUG) log.debug("hello")

Есть ли способ в Scala или Java инициировать переменную DEBUG таким образом, чтобы, если это так, false все выражение удалялось во время выполнения?

Я предполагаю, что ветвление будет удалено JIT во время выполнения, но так ли это? Есть ли способ «подсказать» JIT, чтобы это сделать?

Я думаю, вы можете написать сценарий для регистратора и поместить в него условие if. Java — это язык компилятора, мои знания говорят, что это невозможно.

Nguyen Manh Cuong 10.08.2024 09:34

logger просто для примера. Я предполагаю, что JIT устранит ветвление, но так ли это? Есть ли способ «подсказать» JIT, чтобы это сделать?

Егор Лебедев 10.08.2024 09:38

В Scala вы можете использовать макрос, но вам придется дважды скомпилировать и создать JAR (с ним и без него), поскольку вы не сможете просто вставить его, если он был удален из байт-кода. Я бы просто проверил, хорошо ли новые виртуальные машины удаляют неиспользуемые ветки (они должны это делать).

Mateusz Kubuszok 10.08.2024 09:39

@Mateusz Kubuszok да, я ошибся насчет удаления его во время компиляции. Мне нужен один jar, поэтому на самом деле его следует удалить во время выполнения.

Егор Лебедев 10.08.2024 09:41

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

Егор Лебедев 10.08.2024 09:43

Зачем тебе в банке? Я почти уверен, что компиляторы могут (но не обязательно будут) удалять if, если DEBUG является статическим финалом.

Sweeper 10.08.2024 09:44

Если DEBUG является окончательным (и даже если бы это было не так, но никогда не менялось), я считаю, что JIT, скорее всего, оптимизирует его. Вы можете проверить это, настроив JVM для немедленной компиляции в собственный код, а затем проверив созданную сборку. Но, как говорит Sweeper, на самом деле это одна из немногих оптимизаций, которые javac сделают за вас, если DEBUG на самом деле является постоянным выражением (т. е. окончательным и которому присвоено значение при инициализации) (не уверен насчет компилятора Scala). Итак, если DEBUG является окончательным и инициализируется литералом false, то он даже не будет отображаться в результирующем байт-коде.

Mark Rotteveel 10.08.2024 09:45

@Sweeper Это не обязательно должно быть static, это просто должно быть постоянное значение (final и инициализированное постоянным выражением, например литералом false).

Mark Rotteveel 10.08.2024 09:50

Не пытайтесь давать подсказки JIT, если только вы не определите, что конкретный код является узким местом. Если это важно для производительности, JIT, скорее всего, хорошо его оптимизирует. И даже если это так, измерьте производительность (с помощью JMH) до и после внесения изменений. Кроме того, если у вас есть private static final boolean DEBUG = false;, даже компилятор Java (javac) встроит это значение (и он не выполняет много оптимизаций).

dan1st 10.08.2024 10:05
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Что вы можете сделать

Если значение является константой, отметьте его static final. Это также защитит вас от случайных изменений. Если вам нужно, чтобы переменная была для каждого экземпляра (например, задана в конструкторе), просто сделайте это final. JIT также сможет использовать эту информацию.

Но JIT — не единственный компилятор, способный использовать эту информацию.
Допустим, у вас есть следующий Java-код:

class A{
    static final boolean DEBUG = false;
    void a(){
        if (DEBUG){
            System.out.println("Hello World");
        }
    }
}

и вы посмотрите на байт-код, сгенерированный javac (с использованием javap -c A.class), он будет выглядеть примерно так:

Compiled from "A.java"
class A {
  static final boolean DEBUG;

  A();
    Code:
       0: aload_0
       1: invokespecial #3                  // Method java/lang/Object."<init>":()V
       4: return

  void a();
    Code:
       0: return
}

Как видите, javac (хотя я не знаю о компиляторе Scala) уже способен удалять некоторые ветки, происходящие из полей static final, если они инициализированы литералом. Но это также означает, что вам придется перекомпилировать все классы, используя это поле, если вы измените переменную DEBUG.

Если вы хотите избежать этого и при этом быть уверенным, что JIT оптимизирует это во время выполнения, вы можете сделать что-то вроде:

static final boolean DEBUG = initDebug();
private static boolean initDebug(){
    return DEBUG;
}

Что делает JIT

Теперь, даже если javac не может это встроить, JIT может сделать гораздо больше. Он может видеть, что переменная равна final, предположить, что она никогда не меняется, и оптимизировать ее.

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

Но тебя это не должно волновать, если только тебе не придется

Во многих случаях то, что, по вашему мнению, важно для производительности, не имеет значения. Если вы когда-нибудь решите, что вам следует что-то оптимизировать, сначала проверьте, действительно ли это является узким местом. Для этого вы можете использовать Flamegraphs или аналогичные инструменты.

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

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

Косвенное обращение через метод не является необходимым, чтобы избежать оптимизации: вы также можете избежать javac удаления if, инициализируя его непостоянным выражением (например, Boolean.FALSE вместо false, поскольку распаковка не считается постоянным выражением) или инициализируя его в (статическом) инициализаторе, а не сразу при объявлении.

Mark Rotteveel 10.08.2024 10:37

Да, использование метода — это всего лишь пример того, как это можно сделать (и самый простой для понимания, по моему мнению).

dan1st 10.08.2024 11:01

Я прошу разобраться во внутреннем устройстве, а не в том, как оптимизировать рабочий код, так что не волнуйтесь.

Егор Лебедев 10.08.2024 13:49

Другие вопросы по теме