int 上的余数运算符导致 java.util.Objects.requireNonNull?
Remainder operator on int causes java.util.Objects.requireNonNull?
我正在尝试通过一些内部方法获得尽可能多的性能。
Java代码是:
List<DirectoryTaxonomyWriter> writers = Lists.newArrayList();
private final int taxos = 4;
[...]
@Override
public int getParent(final int globalOrdinal) throws IOException {
final int bin = globalOrdinal % this.taxos;
final int ordinalInBin = globalOrdinal / this.taxos;
return this.writers.get(bin).getParent(ordinalInBin) * this.taxos + bin; //global parent
}
在我的分析器中,我看到 java.util.Objects.requireNonNull
中有 1% CPU 支出,但我什至不这么称呼。检查字节码时,我看到了这个:
public getParent(I)I throws java/io/IOException
L0
LINENUMBER 70 L0
ILOAD 1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
BIPUSH 8
IREM
ISTORE 2
所以编译器生成了这个(无用的?)检查。我在原语上工作,无论如何都不能是 null
,那么为什么编译器会生成这一行?这是一个错误吗?或者 'normal' 行为?
(我可能会使用位掩码,但我只是好奇)
[更新]
运营商似乎与它无关(见下面的回答)
使用 eclipse 编译器(4.10 版)我得到了这个更合理的结果:
public getParent(I)I throws java/io/IOException
L0
LINENUMBER 77 L0
ILOAD 1
ICONST_4
IREM
ISTORE 2
L1
LINENUMBER 78 L
这样更符合逻辑。
看来我的问题是 'wrong' 因为它与运算符无关,而是与字段本身有关。还是不知道为什么..
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
变成:
public test()I
L0
LINENUMBER 51 L0
BIPUSH 7
ISTORE 1
L1
LINENUMBER 52 L1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
ICONST_4
ISTORE 2
L2
LINENUMBER 53 L2
BIPUSH 7
ILOAD 2
IREM
IRETURN
为什么不呢?
假设
class C {
private final int taxos = 4;
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
}
像 c.test()
这样的调用,其中 c
声明为 C
必须 在 c
为 [=16 时抛出=].你的方法等同于
public int test() {
return 3; // `7 % 4`
}
因为您只使用常量。由于 test
是非静态的,因此必须进行检查。通常,它会在访问字段或调用非静态方法时隐式完成,但您不这样做。所以需要一个明确的检查。一种可能是调用 Objects.requireNonNull
.
字节码
不要忘记字节码基本上与性能无关。 javac
的任务是生成 一些 字节码,其执行与您的源代码相对应。这并不是要进行 任何 优化,因为优化后的代码通常更长且更难分析,而字节码 实际上是用于优化的源代码 即时编译器。所以 javac
应该保持简单....
表现
In my profiler I saw there is 1% CPU spend in java.util.Objects.requireNonNull
我首先要归咎于探查器。分析 Java 非常困难,您永远无法期待完美的结果。
您可能应该尝试将方法设为静态。你肯定应该阅读 this article about null checks.
首先,这是此行为的最小可重现示例:
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test {
private final int bar = 5;
/**
* public int foo();
* Code:
* 0: iconst_5
* 1: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
* 4: pop
* 5: iconst_5
* 6: ireturn
*/
public int foo2() {
return this.bar;
}
}
该行为是由于 Java 编译器如何优化 compile-time constants。
请注意,在 foo()
的字节码中,没有访问对象引用来获取 bar
的值。那是因为它是一个编译时常量,因此 JVM 可以简单地对 return 这个值执行 iconst_5
操作。
当将 bar
更改为非编译时间常量(通过删除 final
关键字或不在声明内但在构造函数内初始化)时,您将得到:
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test2 {
private int bar = 5;
/**
* public int foo();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo2() {
return this.bar;
}
}
此对象的 aload_0
pushes the reference of this
onto the operand stack to then get the bar
field。
这里编译器足够聪明,注意到 aload_0
(成员函数中的 this
引用)在逻辑上不能是 null
.
现在你的情况实际上是缺少编译器优化吗?
查看@maaartinus 的回答。
我正在尝试通过一些内部方法获得尽可能多的性能。
Java代码是:
List<DirectoryTaxonomyWriter> writers = Lists.newArrayList();
private final int taxos = 4;
[...]
@Override
public int getParent(final int globalOrdinal) throws IOException {
final int bin = globalOrdinal % this.taxos;
final int ordinalInBin = globalOrdinal / this.taxos;
return this.writers.get(bin).getParent(ordinalInBin) * this.taxos + bin; //global parent
}
在我的分析器中,我看到 java.util.Objects.requireNonNull
中有 1% CPU 支出,但我什至不这么称呼。检查字节码时,我看到了这个:
public getParent(I)I throws java/io/IOException
L0
LINENUMBER 70 L0
ILOAD 1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
BIPUSH 8
IREM
ISTORE 2
所以编译器生成了这个(无用的?)检查。我在原语上工作,无论如何都不能是 null
,那么为什么编译器会生成这一行?这是一个错误吗?或者 'normal' 行为?
(我可能会使用位掩码,但我只是好奇)
[更新]
运营商似乎与它无关(见下面的回答)
使用 eclipse 编译器(4.10 版)我得到了这个更合理的结果:
public getParent(I)I throws java/io/IOException L0 LINENUMBER 77 L0 ILOAD 1 ICONST_4 IREM ISTORE 2 L1 LINENUMBER 78 L
这样更符合逻辑。
看来我的问题是 'wrong' 因为它与运算符无关,而是与字段本身有关。还是不知道为什么..
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
变成:
public test()I
L0
LINENUMBER 51 L0
BIPUSH 7
ISTORE 1
L1
LINENUMBER 52 L1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
ICONST_4
ISTORE 2
L2
LINENUMBER 53 L2
BIPUSH 7
ILOAD 2
IREM
IRETURN
为什么不呢?
假设
class C {
private final int taxos = 4;
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
}
像 c.test()
这样的调用,其中 c
声明为 C
必须 在 c
为 [=16 时抛出=].你的方法等同于
public int test() {
return 3; // `7 % 4`
}
因为您只使用常量。由于 test
是非静态的,因此必须进行检查。通常,它会在访问字段或调用非静态方法时隐式完成,但您不这样做。所以需要一个明确的检查。一种可能是调用 Objects.requireNonNull
.
字节码
不要忘记字节码基本上与性能无关。 javac
的任务是生成 一些 字节码,其执行与您的源代码相对应。这并不是要进行 任何 优化,因为优化后的代码通常更长且更难分析,而字节码 实际上是用于优化的源代码 即时编译器。所以 javac
应该保持简单....
表现
In my profiler I saw there is 1% CPU spend in
java.util.Objects.requireNonNull
我首先要归咎于探查器。分析 Java 非常困难,您永远无法期待完美的结果。
您可能应该尝试将方法设为静态。你肯定应该阅读 this article about null checks.
首先,这是此行为的最小可重现示例:
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test {
private final int bar = 5;
/**
* public int foo();
* Code:
* 0: iconst_5
* 1: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
* 4: pop
* 5: iconst_5
* 6: ireturn
*/
public int foo2() {
return this.bar;
}
}
该行为是由于 Java 编译器如何优化 compile-time constants。
请注意,在 foo()
的字节码中,没有访问对象引用来获取 bar
的值。那是因为它是一个编译时常量,因此 JVM 可以简单地对 return 这个值执行 iconst_5
操作。
当将 bar
更改为非编译时间常量(通过删除 final
关键字或不在声明内但在构造函数内初始化)时,您将得到:
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test2 {
private int bar = 5;
/**
* public int foo();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo2() {
return this.bar;
}
}
此对象的 aload_0
pushes the reference of this
onto the operand stack to then get the bar
field。
这里编译器足够聪明,注意到 aload_0
(成员函数中的 this
引用)在逻辑上不能是 null
.
现在你的情况实际上是缺少编译器优化吗?
查看@maaartinus 的回答。