Например, у меня есть такое выражение
if (DEBUG) log.debug("hello")
Есть ли способ в Scala или Java инициировать переменную DEBUG
таким образом, чтобы, если это так, false
все выражение удалялось во время выполнения?
Я предполагаю, что ветвление будет удалено JIT во время выполнения, но так ли это? Есть ли способ «подсказать» JIT, чтобы это сделать?
logger просто для примера. Я предполагаю, что JIT устранит ветвление, но так ли это? Есть ли способ «подсказать» JIT, чтобы это сделать?
В Scala вы можете использовать макрос, но вам придется дважды скомпилировать и создать JAR (с ним и без него), поскольку вы не сможете просто вставить его, если он был удален из байт-кода. Я бы просто проверил, хорошо ли новые виртуальные машины удаляют неиспользуемые ветки (они должны это делать).
@Mateusz Kubuszok да, я ошибся насчет удаления его во время компиляции. Мне нужен один jar, поэтому на самом деле его следует удалить во время выполнения.
Я отредактировал вопрос, чтобы удалить часть об удалении во время компиляции.
Зачем тебе в банке? Я почти уверен, что компиляторы могут (но не обязательно будут) удалять if
, если DEBUG
является статическим финалом.
Если DEBUG
является окончательным (и даже если бы это было не так, но никогда не менялось), я считаю, что JIT, скорее всего, оптимизирует его. Вы можете проверить это, настроив JVM для немедленной компиляции в собственный код, а затем проверив созданную сборку. Но, как говорит Sweeper, на самом деле это одна из немногих оптимизаций, которые javac
сделают за вас, если DEBUG
на самом деле является постоянным выражением (т. е. окончательным и которому присвоено значение при инициализации) (не уверен насчет компилятора Scala). Итак, если DEBUG
является окончательным и инициализируется литералом false
, то он даже не будет отображаться в результирующем байт-коде.
@Sweeper Это не обязательно должно быть static
, это просто должно быть постоянное значение (final
и инициализированное постоянным выражением, например литералом false
).
Не пытайтесь давать подсказки JIT, если только вы не определите, что конкретный код является узким местом. Если это важно для производительности, JIT, скорее всего, хорошо его оптимизирует. И даже если это так, измерьте производительность (с помощью JMH) до и после внесения изменений. Кроме того, если у вас есть private static final boolean DEBUG = false;
, даже компилятор Java (javac
) встроит это значение (и он не выполняет много оптимизаций).
Если значение является константой, отметьте его 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;
}
Теперь, даже если javac
не может это встроить, JIT может сделать гораздо больше. Он может видеть, что переменная равна final
, предположить, что она никогда не меняется, и оптимизировать ее.
Но даже если результат приходит от метода, который вызывается снова и снова и просто всегда возвращает одно и то же значение, даже если JIT не может встроить это значение, он все равно может обнаружить, что какая-то ветвь никогда не выполняется. В этом случае она обычно заменяет эту ветвь ловушкой деоптимизации (если ее необходимо вызвать, JIT деоптимизирует код и запускается заново). Это произойдет только для кода, который выполняется достаточно часто (а для другого кода вам не нужно беспокоиться, поскольку он обычно не влияет на производительность).
Во многих случаях то, что, по вашему мнению, важно для производительности, не имеет значения. Если вы когда-нибудь решите, что вам следует что-то оптимизировать, сначала проверьте, действительно ли это является узким местом. Для этого вы можете использовать Flamegraphs или аналогичные инструменты.
Как только вы дойдете до оптимизации фрагмента кода, сначала используйте JMH, чтобы измерить производительность без оптимизации. После выполнения оптимизации измерьте ее еще раз (с помощью JMH) и проверьте, улучшилась ли она. Если вы не видите существенного улучшения (или даже ухудшается), оптимизация, вероятно, того не стоит. Вы не знаете, какой код будет работать хорошо, и ваши оптимизации могут ухудшить производительность, поскольку они могут усложнить некоторые JIT-оптимизации или вызвать аналогичные проблемы.
При написании приложений Java не оптимизируйте производительность, если не знаете, что это проблема. JIT гарантирует, что то, что имеет отношение к производительности, хорошо оптимизировано. Не пытайтесь вмешаться в него, если вы не знаете, что часть приложения вызывает проблемы в вашем приложении, и не проверьте, действительно ли ваши «оптимизации» что-то улучшают.
Косвенное обращение через метод не является необходимым, чтобы избежать оптимизации: вы также можете избежать javac
удаления if
, инициализируя его непостоянным выражением (например, Boolean.FALSE
вместо false
, поскольку распаковка не считается постоянным выражением) или инициализируя его в (статическом) инициализаторе, а не сразу при объявлении.
Да, использование метода — это всего лишь пример того, как это можно сделать (и самый простой для понимания, по моему мнению).
Я прошу разобраться во внутреннем устройстве, а не в том, как оптимизировать рабочий код, так что не волнуйтесь.
Я думаю, вы можете написать сценарий для регистратора и поместить в него условие if. Java — это язык компилятора, мои знания говорят, что это невозможно.