Java 如何决定数学表达式中的哪个运算符必须被(取消)装箱?
How does Java decide which operator in a math expression has to be (un)boxed?
我目前正在写关于如何编写有效 Java 代码的学士论文。以下四个代码片段是 JMH 基准测试的一部分,每个方法将执行 100 万次。
public final static int primitiveOnly(int dummy, int add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int primitiveToWrapper(int dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToPrimitive(Integer dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToWrapper(Integer dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
这四种方法的结果是:
- 仅原始:1.7 ms/operation
- primitiveToWrapper: 2.2 ms/operation
- wrapperToPrimitive: 47.5 ms/operation
- wrapperToWrapper: 48.2 ms/operation
此行为的原因是,在 primitiveToWrapper 中的操作期间,Integer 值只需取消装箱,而在 wrapperToPrimitive 中的操作中,必须将第一个操作数装箱到 Integer 中,这会导致昂贵的对象创建。
Java 出现这种行为是否有特定原因?我通读了 The Java Language Specification 但找不到这个问题的答案。
更新:
为了解决有关 return 值的问题(感谢 Phil Anderson),我更新了我的代码。此外,我将基准 class 中的所有整数变量更改为 int。这是新版本:
public final static int primitiveOnly(int dummy, int add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int primitiveToWrapperIntDummy(int dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer primitiveToWrapperIntegerDummy(Integer dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToPrimitiveIntDummy(int dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToPrimitiveIntegerDummy(Integer dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToWrapperIntDummy(int dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToWrapperIntegerDummy(Integer dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
结果是 10 次迭代的平均值(1 次迭代 = 上述每种方法执行 100 万次)。
- 只有原始数据:0.783
- primitiveToWrapper (int dummy): 0.735
- primitiveToWrapper(整数虚拟):24.999
- WrapperToPrimitive(int 虚拟):0.709
- WrapperToPrimitive(整数虚拟):26.782
- WrapperToWrapper (int dummy): 0.764
- WrapperToWrapper(整数虚拟):27.301
现在感觉最终结果更加直观。谢谢大家帮助我。
在代码的第二位中,每次为虚拟对象赋值时,java 都必须将其装入一个 Integer 中,因为这是变量的类型。它不知道您实际上从未在其上调用任何方法,并且它可能是一个简单的 int。
因此,每次它遇到代码 dummy += (add1 + add2);
时,它都必须执行以下操作。
- 开箱假人
- 执行加法
- 将结果装回虚拟
每次for循环都会这样做。
这是因为当dummy
是一个整数时,它的值是不可变的。参见例如Why are Integers immutable in Java?
基本上,在你写dummy += (add1 + add2);
的最后一个方法中,它意味着
dummy = Integer.valueOf(dummy.intValue() + add1.intValue() + add2.intValue());
每次循环,都需要分配一个新的对象来保存一个新的整数值。
我目前正在写关于如何编写有效 Java 代码的学士论文。以下四个代码片段是 JMH 基准测试的一部分,每个方法将执行 100 万次。
public final static int primitiveOnly(int dummy, int add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int primitiveToWrapper(int dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToPrimitive(Integer dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToWrapper(Integer dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
这四种方法的结果是:
- 仅原始:1.7 ms/operation
- primitiveToWrapper: 2.2 ms/operation
- wrapperToPrimitive: 47.5 ms/operation
- wrapperToWrapper: 48.2 ms/operation
此行为的原因是,在 primitiveToWrapper 中的操作期间,Integer 值只需取消装箱,而在 wrapperToPrimitive 中的操作中,必须将第一个操作数装箱到 Integer 中,这会导致昂贵的对象创建。
Java 出现这种行为是否有特定原因?我通读了 The Java Language Specification 但找不到这个问题的答案。
更新:
为了解决有关 return 值的问题(感谢 Phil Anderson),我更新了我的代码。此外,我将基准 class 中的所有整数变量更改为 int。这是新版本:
public final static int primitiveOnly(int dummy, int add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int primitiveToWrapperIntDummy(int dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer primitiveToWrapperIntegerDummy(Integer dummy, int add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToPrimitiveIntDummy(int dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToPrimitiveIntegerDummy(Integer dummy, Integer add1, int add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static int wrapperToWrapperIntDummy(int dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
public final static Integer wrapperToWrapperIntegerDummy(Integer dummy, Integer add1, Integer add2) {
for(int i = 0; i < 10; i++) {
dummy += (add1 + add2);
}
return dummy;
}
结果是 10 次迭代的平均值(1 次迭代 = 上述每种方法执行 100 万次)。
- 只有原始数据:0.783
- primitiveToWrapper (int dummy): 0.735
- primitiveToWrapper(整数虚拟):24.999
- WrapperToPrimitive(int 虚拟):0.709
- WrapperToPrimitive(整数虚拟):26.782
- WrapperToWrapper (int dummy): 0.764
- WrapperToWrapper(整数虚拟):27.301
现在感觉最终结果更加直观。谢谢大家帮助我。
在代码的第二位中,每次为虚拟对象赋值时,java 都必须将其装入一个 Integer 中,因为这是变量的类型。它不知道您实际上从未在其上调用任何方法,并且它可能是一个简单的 int。
因此,每次它遇到代码 dummy += (add1 + add2);
时,它都必须执行以下操作。
- 开箱假人
- 执行加法
- 将结果装回虚拟
每次for循环都会这样做。
这是因为当dummy
是一个整数时,它的值是不可变的。参见例如Why are Integers immutable in Java?
基本上,在你写dummy += (add1 + add2);
的最后一个方法中,它意味着
dummy = Integer.valueOf(dummy.intValue() + add1.intValue() + add2.intValue());
每次循环,都需要分配一个新的对象来保存一个新的整数值。