I'm writing a Java program that requires thousands of System.out.println()
statements that will be printed hundreds of millions (or billions) of times throughout the lifecycle of the program for debugging purposes:
if (GVar.runInDebugMode) System.out.println("Print debug message");
In the real world, these statements can be deactivated in order to speed up a computational heavy calculation.
If I set:
public final static boolean runInDebugMode = false;
Does the compiler re-evaluate runInDebugMode
each time it comes across a statement like: if (GVar.runInDebugMode)
or since it was declared as final it will be evaluated once at the beginning of the program and won't put additional strain on the CPU? In other words, would I be better off commenting out all debug statements entirely once I deploy the app or is setting runInDebugMode
to false
sufficient?
CodePudding user response:
The scenario you have is described as "conditional compilation", i.e. the compiler that produces bytecode can optimize away the code if the constant variable is false:
"...in order to allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules differ.
As an example, the following statement results in a compile-time error:
while (false) { x=3; }
because the statement x=3; is not reachable; but the superficially similar case:
if (false) { x=3; }
does not result in a compile-time error. An optimizing compiler may realize that the statement x=3;
will never be executed and may choose to omit the code for that statement from the generated class
file, but the statement x=3;
is not regarded as "unreachable" in the technical sense specified here."
Note the part in bold: it depends on the compiler but it's quite easy to verify with the javap
disassembler, just run javap -v -l -p MyClass.class
to view the resulting bytecode. I'm pretty sure the Oracle/OpenJDK javac does that optimization since long ago and so do most compilers. Note that GVar.runInDebugMode
is a constant expression, so it benefits from this optimization.
Keep in mind:
"Conditional compilation comes with a caveat. If a set of classes that use a "flag" variable - or more precisely, any static constant variable (§4.12.4) - are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag. The classes that use the flag will not see its new value, so their behavior may be surprising. In essence, a change to the value of a flag is binary compatible with pre-existing binaries (no LinkageError occurs) but not behaviorally compatible."
which basically says that you have to also re-compile the code that uses the flag, in addition to the class that defines it. This shouldn't be a problem if you build the whole code.
CodePudding user response:
When you declare a variable like
public final static boolean runInDebugMode = false;
it’s a compile-time constant.
A constant variable is a
final
variable of primitive type or typeString
that is initialized with a constant expression (§15.29).
A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value
V
denoted by the constant variable's initializer.If such a field is
static
, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field.
In other words, when you write if(runInDebugMode)
anywhere and runInDebugMode
is false
at compile time, the behavior is as if you’ve written if(false)
, as the value must be resolved at compile time and no reference to the field appears in the compiled class file.
Your use case has been discussed specifically in §14.22
However, in order to allow the
if
statement to be used conveniently for "conditional compilation" purposes, the actual rules differ.As an example, the following statement results in a compile-time error:
while (false) { x=3; }
because the statement
x=3;
is not reachable; but the superficially similar case:if (false) { x=3; }
does not result in a compile-time error. An optimizing compiler may realize that the statement
x=3;
will never be executed and may choose to omit the code for that statement from the generatedclass
file, but the statementx=3;
is not regarded as "unreachable" in the technical sense specified here.The rationale for this differing treatment is to allow programmers to define "flag" variables such as:
static final boolean DEBUG = false;
and then write code such as:
if (DEBUG) { x=3; }
The idea is that it should be possible to change the value of
DEBUG
fromfalse
totrue
or fromtrue
tofalse
and then compile the code correctly with no other changes to the program text.Conditional compilation comes with a caveat. If a set of classes that use a "flag" variable - or more precisely, any
static
constant variable (§4.12.4) - are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag.
So, this statement makes clear that this form of conditional compilation matches the intent of the language designers and that compilers are entitled to omit the code in question (all relevant compilers do). In principle, a compiler is not required to omit the code, but since it must not generate a reference to the field GVar.runInDebugMode
in the compiled code, the code can’t contain a real conditional. If the code is not omitted, it must be skipped in a de-facto unconditional way. Either, by a goto
instruction or, when compiling in the most naïve way imaginable, by literally testing false
, iconst_0; ifeq …
. Both approaches would be on the nanosecond scale in interpreted execution mode and no challenge to the JIT compiler/ optimizer at all.
It’s worth mentioning that static final
fields are trusted fields which are normally not even changeable by Reflection. This is used, e.g. by the Assertion feature, as under the hood, a class containing an assert
statement will have a static final boolean
field initialized at class initialization time (so it’s not a compile-time constant) and each assert
statement will skip its check conditionally, depending on the state of the static final
variable. It was as early as at Java 1.4 time, when it was concluded that the necessary dead code elimination is commonplace in JVMs, to rely on it in this way.
So even if you turn your debug flag from compile-time constant to an initialization-time constant, the impact on the performance would be hardly noticeable. But the way you’re using it now, the code is removed at compile-time already and doesn’t rely on the JVM anyway.