int上的余数运算符会导致java.util.Objects.requireNonNull?


12

我正在尝试通过某种内部方法获得尽可能多的性能。

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
}

在我的探查器中,我看到其中有1%的CPU支出java.util.Objects.requireNonNull,但我什至没有这样说。在检查字节码时,我看到了:

 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无论如何都不能,所以为什么编译器会生成这一行?是虫子吗?还是“正常”行为?

(我可能会用位掩码解决,但我很好奇)

[更新]

  1. 操作员似乎与它无关(请参阅下面的答案)

  2. 使用eclipse编译器(版本4.10),我得到以下更合理的结果:

    公共getParent(I)我抛出java / io / IOException 
       L0
        线号77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        线数78公升

因此,这更合乎逻辑。


@Lino当然可以,但这与第70行并没有真正的关系,其原因是INVOKESTATIC
RobAu

您使用什么编译器?普通javac不会生成此。
apangin

您使用什么编译器?Java版本,Openjdk / Oracle /等?编辑:哎呀,@ apangin更快,对不起
lugiorgi

1
它是从Intellij 2019.3编译的,具有Java 11, openjdk version "11.0.6" 2020-01-14在ubuntu 64位上。
RobAu

Answers:


3

为什么不?

假设

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 必须时抛出cnull。您的方法等效于

    public int test() {
        return 3; // `7 % 4`
    }

当您仅使用常量时。随着test是非静态的,检查必须做到的。通常,当访问字段或调用非静态方法时,它将隐式完成,但您不这样做。因此,需要进行明确的检查。一种可能性是打电话给Objects.requireNonNull

字节码

不要忘记字节码与性能基本上无关。的任务javac是生产一些字节码,其执行与您的源代码相对应。这并不意味着要进行任何优化,因为优化后的代码通常更长且更难分析,而字节码实际上是优化JIT编译器的源代码。因此javac,希望保持简单。

表现

在我的分析器中,我发现其中有1%的CPU支出 java.util.Objects.requireNonNull

我首先要怪事件探查器。对Java进行性能分析非常困难,您永远无法期望获得完美的结果。

您可能应该尝试使该方法静态化。您一定应该阅读有关空检查的这篇文章


1
感谢@maaartinus的深刻见解。我一定会阅读您的链接文章。
RobAu

1
其实“随着测试是非静态的,检查一定要做”,没有理由要测试是否this是非null。就像您自己说的那样,like之类的调用c.test()必须在cis 失败,null并且必须立即失败,而不是输入方法。因此,在中test()this永远不会存在null(否则将存在JVM错误)。因此,无需检查。实际的解决方法应该是将字段更改taxosstatic,因为在每个实例中都没有必要为编译时常量保留内存。于是,无论test()static是无关紧要的。
Holger

2

好吧,似乎我的问题是“错误的”,因为它与运算符无关,而与字段本身无关。还是不知道为什么。

   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

1
编译器实际上可以担心this引用null吗?这可能吗?
atalantus

1
否,那没有任何意义,除非编译器以Integer某种方式将字段编译,这是自动装箱的结果?
RobAu

1
没有ALOAD 0参考this吗?因此,编译器添加一个nullcheck是有意义的(不是真的)
Lino

1
因此,编译器实际上为添加了空检查this?太好了:/
RobAu

1
我将尝试在命令行中编写最少的代码以javac进行明天的验证。如果这也显示了这种行为,我认为可能是javac错误?
RobAu

2

首先,这是此行为的最小可复制示例:

/**
 * 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编译器如何优化编译时常量

请注意,在foo()没有对象引用的字节码中,将获取的值 bar。那是因为它是一个编译时常量,因此JVM可以简单地执行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推动所述参考this压入操作数堆栈,然后获得bar字段此对象。

在这里,编译器足够聪明,可以注意到aload_0(在this成员函数的情况下,引用在逻辑上不能为)null

现在您的情况实际上是缺少的编译器优化吗?

参见@maaartinus答案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.