Why does Java have an IINC bytecode instruction?

前提是你 提交于 2021-01-29 03:37:48

问题


Why does Java have an IINC bytecode instruction? There is already an IADD bytecode instruction that can be used to accomplish the same.

So why does IINC exist?


回答1:


Only the original designers of Java can answer why they made particular design decisions. However, we can speculate:

IINC does not let you do anything that can't already be accomplished by a ILOAD/SIPUSH/IADD/ISTORE combo. The difference is that IINC is a single instruction, which only takes 3 or 6 bytes, while the 4 instruction sequence is obviously longer. So IINC slightly reduces the size of bytecode that uses it.

Apart from that, early versions of Java used an interpreter, where every instruction has overhead during execution. In this case, using a single IINC instruction could be faster than the equivalent alternative bytecode sequence. Note that JITting has made this largely irrelevant, but IINC dates back to the original version of Java.




回答2:


As already pointed out a single iinc instruction is shorter than the a iload, sipush, iadd, istore sequence. There is also evidence, that performing a common-case code size reduction was an important motivation.

There are specialized instructions for dealing with the first four local variables, e.g. aload_0 does the same as aload 0 and it will be used often for loading the this reference on the operand stack. There’s an ldc instruction being able to refer to one of the first 255 constant pool items whereas all of them could be handled by ldc_w, branch instructions use two bytes for offsets, so only overly large methods have to resort to goto_w, and iconst_n instructions for -1 to 5 exist despite these all could be handled by bipush which supports values which all also could all be handled by sipush, which could be superseded by ldc.

So asymmetric instructions are the norm. In typical applications, there are a lot of small methods with only a few local variables and smaller numbers are more common than larger numbers. iinc is a direct equivalent to stand-alone i++ or i+=smallConstantNumber expressions (applied to local variables) which often occur within loops. By being able to express common code idioms in more compact code without loosing the ability to express all code, you’ll get great savings in overall code size.

As also already pointed out, there is only a slight opportunity for faster execution in interpreted executions which is irrelevant for compiled/optimized code execution.




回答3:


Looking at this table, there are a couple important differences.

iinc: increment local variable #index by signed byte const

  1. iinc uses a register instead of the stack.
  2. iinc can only increment by a signed byte value. If you want to add [-128,127] to an integer, then you could use iinc, but as soon as you want to add a number outside that range you would need to use isub, iadd, or multiple iinc instructions.

E1:

TL;DR

I was basically right, except that the limit is signed short values (16 bits [-32768,32767]). There's a wide bytecode instruction which modifies iinc (and a couple other instructions) to use 16 bit numbers instead of 8 bit numbers.

Additionally, consider adding two variables together. If one of the variables is not constant, the compiler will never be able to inline its value to bytecode, so it cannot use iinc; it will have to use iadd.


package SO37056714;

public class IntegerIncrementTest {
  public static void main(String[] args) {
    int i = 1;
    i += 5;
  }
}

I'm going to be experimenting with the above piece of code. As it is, is uses iinc, as expected.

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 5
       5: return
}

i += 127 uses iinc as expected.

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 127
       5: return
}

i += 128 does not use iinc anymore, but instead iinc_w:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc_w        1, 128
       8: return
}

i -= 601 also uses iinc_w:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iinc_w        1, -601
       8: return
}

The _w suffix refers to the wide bytecode, which allows for constants up to 16 bits ([-32768, 32767]).

If we try i += 32768, we will see what I predicted above:

$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: ldc           #16                 // int 32768
       5: iadd
       6: istore_1
       7: return
}

Additionally, consider the case where we are adding another variable to i (i += c). The compiler doesn't know if c is constant or not, so it cannot inline c's value to bytecode. It will use iadd for this case too:

int i = 1;
byte c = 3;
i += c;
$ javap -c IntegerIncrementTest.class 
Compiled from "IntegerIncrementTest.java"
public class SO37056714.IntegerIncrementTest {
  public SO37056714.IntegerIncrementTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_3
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_1
       8: return
}


来源:https://stackoverflow.com/questions/37056714/why-does-java-have-an-iinc-bytecode-instruction

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!