You Write Java, JVM Rewrites It!

Hello Java developers. Welcome to my new article. Today’s topic is Constant Folding.

What is Constant Folding?

Constant folding is a crucial optimization technique used by the Java Virtual Machine (JVM) and compiler to enhance performance. It is not only utilized in JVM, but also used in other modern languages’ compilers.

This technique eliminates redundant computations by evaluating constant expressions at compile-time rather than runtime. This results in faster execution and reduced bytecode size.

For example, consider the following Java code:

<span>public</span> <span>class</span> <span>ConstantFoldingExample</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span>
<span>int</span> <span>x</span> <span>=</span> <span>5</span> <span>+</span> <span>10</span><span>;</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>x</span><span>);</span>
<span>}</span>
<span>}</span>
<span>public</span> <span>class</span> <span>ConstantFoldingExample</span> <span>{</span>
    <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span>
        <span>int</span> <span>x</span> <span>=</span> <span>5</span> <span>+</span> <span>10</span><span>;</span>
        <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>x</span><span>);</span>
    <span>}</span>
<span>}</span>
public class ConstantFoldingExample { public static void main(String[] args) { int x = 5 + 10; System.out.println(x); } }

Enter fullscreen mode Exit fullscreen mode

The Java compiler recognizes that 5 + 10 is a constant expression and simplifies it to 15. As a result, the compiled bytecode does not contain an addition operation but directly loads 15 into the variable.

Let’s explore the bytecode by using javap command line tool. Execute following lines in your terminal.

<span>$ </span>javac ConstantFoldingExample.java
<span>$ </span>javap <span>-c</span> ConstantFoldingExample
<span>$ </span>javac ConstantFoldingExample.java
<span>$ </span>javap <span>-c</span> ConstantFoldingExample
$ javac ConstantFoldingExample.java $ javap -c ConstantFoldingExample

Enter fullscreen mode Exit fullscreen mode

The compiled bytecode might look like following:

<span>0</span><span>:</span> <span>bipush</span> <span>15</span> <span>// Push the constant value 15 onto the stack</span>
<span>2</span><span>:</span> <span>istore_1</span> <span>// Store it in variable x</span>
<span>3</span><span>:</span> <span>getstatic</span> <span>java</span><span>/</span><span>lang</span><span>/</span><span>System</span><span>/</span><span>out</span> <span>Ljava</span><span>/</span><span>io</span><span>/</span><span>PrintStream</span><span>;</span>
<span>6</span><span>:</span> <span>iload_1</span> <span>// Load x (which is 15)</span>
<span>7</span><span>:</span> <span>invokevirtual</span> <span>java</span><span>/</span><span>io</span><span>/</span><span>PrintStream</span><span>.</span><span>println</span> <span>(</span><span>I</span><span>)</span><span>V</span>
<span>10</span><span>:</span> <span>return</span>
 <span>0</span><span>:</span> <span>bipush</span> <span>15</span>  <span>// Push the constant value 15 onto the stack</span>
 <span>2</span><span>:</span> <span>istore_1</span>   <span>// Store it in variable x</span>
 <span>3</span><span>:</span> <span>getstatic</span> <span>java</span><span>/</span><span>lang</span><span>/</span><span>System</span><span>/</span><span>out</span> <span>Ljava</span><span>/</span><span>io</span><span>/</span><span>PrintStream</span><span>;</span>
 <span>6</span><span>:</span> <span>iload_1</span>    <span>// Load x (which is 15)</span>
 <span>7</span><span>:</span> <span>invokevirtual</span> <span>java</span><span>/</span><span>io</span><span>/</span><span>PrintStream</span><span>.</span><span>println</span> <span>(</span><span>I</span><span>)</span><span>V</span>
 <span>10</span><span>:</span> <span>return</span>
0: bipush 15 // Push the constant value 15 onto the stack 2: istore_1 // Store it in variable x 3: getstatic java/lang/System/out Ljava/io/PrintStream; 6: iload_1 // Load x (which is 15) 7: invokevirtual java/io/PrintStream.println (I)V 10: return

Enter fullscreen mode Exit fullscreen mode

Does Constant Folding Occur in Only Arithmetic Operations?

The short answer is no. It occurs in following cases.

  • Arithmetic Operations (Addition, Subtraction, Multiplication and so on)
  • String Concatenation
  • Boolean

We’ve already seen how constant folding occurs in arithmetic operations, let’s see other two.

String Concatenation

Consider following Java code

<span>public</span> <span>class</span> <span>ConstantFoldingWithStrings</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
<span>String</span> <span>message</span> <span>=</span> <span>"Hello"</span> <span>+</span> <span>"World"</span><span>;</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Message: "</span> <span>+</span> <span>message</span><span>);</span>
<span>}</span>
<span>}</span>
<span>public</span> <span>class</span> <span>ConstantFoldingWithStrings</span> <span>{</span>

  <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
      <span>String</span> <span>message</span> <span>=</span> <span>"Hello"</span> <span>+</span> <span>"World"</span><span>;</span>
      <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Message: "</span> <span>+</span> <span>message</span><span>);</span>
   <span>}</span>
<span>}</span>
public class ConstantFoldingWithStrings { public static void main(String... args) { String message = "Hello" + "World"; System.out.println("Message: " + message); } }

Enter fullscreen mode Exit fullscreen mode

Do the same actions as you did in arithmetic operation in order to get the bytecode.

<span>0</span><span>:</span> <span>ldc</span> <span>#</span><span>7</span> <span>// String HelloWorld</span>
<span>0</span><span>:</span> <span>ldc</span>    <span>#</span><span>7</span> <span>// String HelloWorld</span>
0: ldc #7 // String HelloWorld

Enter fullscreen mode Exit fullscreen mode

The output is truncated for simplicity. No need to say anything, it is obvious from the result.

Boolean Folding

Boolean folding might confuse you, but we will see the both possible options ( false and true case ).

true case:

<span>public</span> <span>class</span> <span>ConstantFoldingWithBooleans</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
<span>boolean</span> <span>isBool</span> <span>=</span> <span>3</span> <span><</span> <span>5</span> <span>&&</span> <span>6</span> <span><</span> <span>8</span><span>;</span> <span>// true</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Bool: "</span> <span>+</span> <span>isBool</span><span>);</span>
<span>}</span>
<span>}</span>
<span>public</span> <span>class</span> <span>ConstantFoldingWithBooleans</span> <span>{</span>

   <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
      <span>boolean</span> <span>isBool</span> <span>=</span> <span>3</span> <span><</span> <span>5</span> <span>&&</span> <span>6</span> <span><</span> <span>8</span><span>;</span> <span>// true</span>
      <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Bool: "</span> <span>+</span> <span>isBool</span><span>);</span>
   <span>}</span>
<span>}</span>
public class ConstantFoldingWithBooleans { public static void main(String... args) { boolean isBool = 3 < 5 && 6 < 8; // true System.out.println("Bool: " + isBool); } }

Enter fullscreen mode Exit fullscreen mode

Here’s the generated bytecode:

<span>0</span><span>:</span> <span>iconst_1</span>
<span>1</span><span>:</span> <span>istore_1</span>
<span>0</span><span>:</span> <span>iconst_1</span>
<span>1</span><span>:</span> <span>istore_1</span>
0: iconst_1 1: istore_1

Enter fullscreen mode Exit fullscreen mode

iconst_1 opcode means ‘Push int constant 1‘ and in 1 means true.

Let’s see the false case:

<span>public</span> <span>class</span> <span>ConstantFoldingWithBooleans</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
<span>boolean</span> <span>isBool</span> <span>=</span> <span>3</span> <span>></span> <span>5</span> <span>&&</span> <span>6</span> <span><</span> <span>8</span><span>;</span> <span>// false</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Bool: "</span> <span>+</span> <span>isBool</span><span>);</span>
<span>}</span>
<span>}</span>
<span>public</span> <span>class</span> <span>ConstantFoldingWithBooleans</span> <span>{</span>

   <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
      <span>boolean</span> <span>isBool</span> <span>=</span> <span>3</span> <span>></span> <span>5</span> <span>&&</span> <span>6</span> <span><</span> <span>8</span><span>;</span> <span>// false</span>
      <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Bool: "</span> <span>+</span> <span>isBool</span><span>);</span>
   <span>}</span>
<span>}</span>
public class ConstantFoldingWithBooleans { public static void main(String... args) { boolean isBool = 3 > 5 && 6 < 8; // false System.out.println("Bool: " + isBool); } }

Enter fullscreen mode Exit fullscreen mode

Generated bytecode:

<span>0</span><span>:</span> <span>iconst_0</span>
<span>1</span><span>:</span> <span>istore_1</span>
<span>0</span><span>:</span> <span>iconst_0</span>
<span>1</span><span>:</span> <span>istore_1</span>
0: iconst_0 1: istore_1

Enter fullscreen mode Exit fullscreen mode

iconst_0 opcode means ‘Push int constant 0‘ and in 0 means false.

Tricky Example

Most of the time, we don’t write code this way by embedding constants directly into expressions. Instead, we store them in variables for better readability and maintainability. However, constant folding will happen when the variables are declared as final.

Consider following example

<span>public</span> <span>class</span> <span>ConstantFolding</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
<span>final</span> <span>int</span> <span>campaignPoints</span> <span>=</span> <span>2000</span><span>;</span>
<span>final</span> <span>int</span> <span>rate</span> <span>=</span> <span>2</span><span>;</span>
<span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Amount: "</span> <span>+</span> <span>amount</span><span>);</span>
<span>}</span>
<span>public</span> <span>static</span> <span>int</span> <span>getTransactionAmount</span><span>()</span> <span>{</span>
<span>return</span> <span>3000</span><span>;</span>
<span>}</span>
<span>}</span>
<span>public</span> <span>class</span> <span>ConstantFolding</span> <span>{</span>
   <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>...</span> <span>args</span><span>)</span> <span>{</span>
      <span>final</span> <span>int</span> <span>campaignPoints</span> <span>=</span> <span>2000</span><span>;</span>
      <span>final</span> <span>int</span> <span>rate</span> <span>=</span> <span>2</span><span>;</span>
      <span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
      <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Amount: "</span> <span>+</span> <span>amount</span><span>);</span>
   <span>}</span>

   <span>public</span> <span>static</span> <span>int</span> <span>getTransactionAmount</span><span>()</span> <span>{</span>
      <span>return</span> <span>3000</span><span>;</span>
   <span>}</span>
<span>}</span>
public class ConstantFolding { public static void main(String... args) { final int campaignPoints = 2000; final int rate = 2; int amount = getTransactionAmount() * campaignPoints * rate; System.out.println("Amount: " + amount); } public static int getTransactionAmount() { return 3000; } }

Enter fullscreen mode Exit fullscreen mode

In above example, if we don’t write final keyword in front of the variables, constant folding will not happen at compile time. Let’s see what will be generated as bytecode.

<span>0</span><span>:</span> <span>invokestatic</span> <span>#</span><span>7</span> <span>// Method getTransactionAmount:()I</span>
<span>3</span><span>:</span> <span>sipush</span> <span>2000</span>
<span>6</span><span>:</span> <span>imul</span>
<span>7</span><span>:</span> <span>iconst_2</span>
<span>8</span><span>:</span> <span>imul</span>
<span>9</span><span>:</span> <span>istore_1</span>
<span>0</span><span>:</span> <span>invokestatic</span>  <span>#</span><span>7</span>    <span>// Method getTransactionAmount:()I</span>
<span>3</span><span>:</span> <span>sipush</span>        <span>2000</span>
<span>6</span><span>:</span> <span>imul</span>
<span>7</span><span>:</span> <span>iconst_2</span>
<span>8</span><span>:</span> <span>imul</span>
<span>9</span><span>:</span> <span>istore_1</span>
0: invokestatic #7 // Method getTransactionAmount:()I 3: sipush 2000 6: imul 7: iconst_2 8: imul 9: istore_1

Enter fullscreen mode Exit fullscreen mode

Constant Folding didn’t occur regardless of final keywords. Why?

The trick is hidden behind the equation.

<span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
<span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
int amount = getTransactionAmount() * campaignPoints * rate;

Enter fullscreen mode Exit fullscreen mode

Java is calculates the arithmetic operations from left to right. So first operand ( getTransactionAmount() ) is not final. This is the reason why Constant Folding didn’t occur.

If we wrap the constant variables with parentheses or switch the operands positions, then Constant Folding will occur.

<span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>(</span><span>campaignPoints</span> <span>*</span> <span>rate</span><span>);</span>
<span>// or</span>
<span>int</span> <span>amount</span> <span>=</span> <span>campaignPoints</span> <span>*</span> <span>rate</span> <span>*</span> <span>getTransactionAmount</span><span>();</span>
<span>// or</span>
<span>final</span> <span>int</span> <span>multiplier</span> <span>=</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
<span>int</span> <span>amount</span> <span>=</span> <span>multiplier</span> <span>*</span> <span>getTransactionAmount</span><span>();</span>
<span>int</span> <span>amount</span> <span>=</span> <span>getTransactionAmount</span><span>()</span> <span>*</span> <span>(</span><span>campaignPoints</span> <span>*</span> <span>rate</span><span>);</span>

<span>// or</span>

<span>int</span> <span>amount</span> <span>=</span> <span>campaignPoints</span> <span>*</span> <span>rate</span> <span>*</span> <span>getTransactionAmount</span><span>();</span>

<span>// or</span>

<span>final</span> <span>int</span> <span>multiplier</span> <span>=</span> <span>campaignPoints</span> <span>*</span> <span>rate</span><span>;</span>
<span>int</span> <span>amount</span> <span>=</span> <span>multiplier</span> <span>*</span> <span>getTransactionAmount</span><span>();</span>
int amount = getTransactionAmount() * (campaignPoints * rate); // or int amount = campaignPoints * rate * getTransactionAmount(); // or final int multiplier = campaignPoints * rate; int amount = multiplier * getTransactionAmount();

Enter fullscreen mode Exit fullscreen mode

You can choose one of the above. Here’s the generated bytecode

<span>0</span><span>:</span> <span>invokestatic</span> <span>#</span><span>7</span> <span>// Method getTransactionAmount:()I</span>
<span>3</span><span>:</span> <span>sipush</span> <span>4000</span> <span>// 2000 * 2 = 4000</span>
<span>6</span><span>:</span> <span>imul</span>
<span>7</span><span>:</span> <span>istore_1</span>
<span>0</span><span>:</span> <span>invokestatic</span>  <span>#</span><span>7</span>    <span>// Method getTransactionAmount:()I</span>
<span>3</span><span>:</span> <span>sipush</span>        <span>4000</span>  <span>// 2000 * 2 = 4000</span>
<span>6</span><span>:</span> <span>imul</span>
<span>7</span><span>:</span> <span>istore_1</span>
0: invokestatic #7 // Method getTransactionAmount:()I 3: sipush 4000 // 2000 * 2 = 4000 6: imul 7: istore_1

Enter fullscreen mode Exit fullscreen mode

This time Constant Folding occurred.

Benefits of Constant Folding

  • Improves performance by eliminating redundant computations.
  • Reduces bytecode size.
  • JVM executes optimized bytecode faster

Conclusion

Constant folding is a powerful optimization that enhances Java program efficiency by precomputing constant expressions.

As always, source code is available on GitHub.

原文链接:You Write Java, JVM Rewrites It!

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
A man's best friends are his ten fingers.
人最好的朋友是自己的十个手指
评论 抢沙发

请登录后发表评论

    暂无评论内容