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