为什么Java有IINC
字节码指令?已经有一个IADD
字节码指令可用于完成相同的操作。
那么为什么IINC
存在?
只有Java的原始设计者才能回答他们做出特定设计决策的原因。但是,我们可以推测:
IINC
不允许你做一些ILOAD
/ SIPUSH
/ IADD
/ ISTORE
组合无法完成的任何事情。不同之处在于IINC
是单指令,只需要3或6个字节,而4指令序列显然更长。所以IINC
略微减少了使用它的字节码的大小。
除此之外,早期版本的Java使用了解释器,其中每个指令在执行期间都有开销。在这种情况下,使用单个IINC
指令可能比等效的替代字节码序列更快。请注意,JITting使这在很大程度上无关紧要,但IINC
可以追溯到Java的原始版本。
作为already pointed out,单个iinc
指令比iload
,sipush
,iadd
,istore
序列短。还有证据表明,执行常见的代码大小减少是一个重要的动机。
有处理前四个局部变量的专门指令,例如: aload_0
与aload 0
的作用相同,它经常用于在操作数堆栈上加载this
引用。有一个ldc
指令能够引用前255个常量池项中的一个,而所有这些都可以由ldc_w
处理,分支指令使用两个字节进行偏移,因此只有过大的方法必须求助于goto_w
和iconst_n
指令-1
到5
存在,尽管这些都可以由bipush
处理,sipush
支持所有也可以全部由ldc
处理的值,iinc
可以被i++
取代。
因此,不对称指令是常态。在典型的应用程序中,有许多小方法只有少数局部变量,较小的数字比较大的数字更常见。 i+=smallConstantNumber
直接等同于独立的this table或iinc
表达式(应用于局部变量),它们通常出现在循环中。通过在更简洁的代码中表达公共代码习惯用语而不失去表达所有代码的能力,您将大大节省整体代码大小。
正如已经指出的那样,在解释的执行中只有很少的机会可以更快地执行,这与编译/优化的代码执行无关。
看看register,有几个重要的区别。
iinc:通过带符号的字节const增加局部变量#index
iinc
使用[-128,127]
而不是堆栈。iinc
只能通过有符号字节值递增。如果你想将isub
添加到一个整数,那么你可以使用iadd
,但是只要你想要在该范围之外添加一个数字,就需要使用iinc
,[-32768,32767]
或多个wide
指令。Q1:
我基本上是正确的,除了限制是有符号的短值(16位iinc
)。有一个iinc
字节码指令修改iadd
(和其他一些指令)使用16位数而不是8位数。
另外,考虑将两个变量一起添加。如果其中一个变量不是常量,编译器将永远无法将其值内联到字节码,因此它不能使用package SO37056714;
public class IntegerIncrementTest {
public static void main(String[] args) {
int i = 1;
i += 5;
}
}
;它将不得不使用iinc
。
$ 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
。
iinc
$ 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
。
iinc
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
:
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
}
也使用_w
:
wide
[-32768, 32767]
后缀是指i += 32768
字节码,它允许常数高达16位($ 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
}
)。
如果我们尝试i
,我们将看到我上面所预测的:
i += c
另外,考虑我们将另一个变量添加到c
(c
)的情况。编译器不知道iadd
是否是常量,因此它不能将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
}
:
qazxswpoi
qazxswpoi