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
}

プロファイラーでjava.util.Objects.requireNonNull、にCPU使用率が1%あるのを見ましたが、それを呼び出すことすらしません。バイトコードを検査するとき、私はこれを見ました:

 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)を使用すると、より妥当な結果が得られます。

    public getParent(I)I java / io / IOExceptionをスローします 
       L0
        LINENUMBER 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        LINENUMBER 78 L

つまり、それはより論理的です。


@Lino確かに、それは原因のある行70には実際には関係ありませんINVOKESTATIC
RobAu

どのコンパイラを使用していますか?通常javacはこれを生成しません。
アパンギン

どのコンパイラを使用していますか?Javaバージョン、Openjdk / Oracle / etc?編集:whops、@ apanginはより高速で、申し訳ありません
でした

1
Intellij 2019.3から、Java 11、openjdk version "11.0.6" 2020-01-14ubuntu 64ビットでコンパイルされています。
RobAu

回答:


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 なければならない時に投げcていますnull。あなたの方法は

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

定数のみを扱うので。test非静的であること、チェックが行わなければなりません。通常、フィールドがアクセスされたとき、または非静的メソッドが呼び出されたときに暗黙的に行われますが、それは行いません。したがって、明示的なチェックが必要です。1つの可能性はを呼び出すことObjects.requireNonNullです。

バイトコード

バイトコードは基本的にパフォーマンスには無関係であることを忘れないでください。のタスクは、実行がソースコードに対応javacするいくつかのバイトコードを生成することです。バイトコードは実際には最適化JITコンパイラーのソースコードですが、最適化されたコードは通常より長く分析が難しいため、最適化を行うことを意図していませ。だからそれをシンプルに保つことが期待されています....javac

パフォーマンス

プロファイラーで、CPU使用率が1%あることを確認しました java.util.Objects.requireNonNull

最初にプロファイラーを責めます。Javaのプロファイリングはかなり難しく、完璧な結果を期待することはできません。

メソッドを静的にしてみてください。nullチェックに関するこの記事を必ずお読みください


1
洞察に満ちた答えを@maaartinusに感謝します。リンク先の記事はきっと読み上げます。
RobAu

1
「テストが非静的であるため、チェックを実行する必要があります」実際、がthis非であるかどうかをテストする理由はありませんnull。あなたが言ったように、のような呼び出しはisのc.test()ときに失敗しなければならず、メソッドに入るのではなく、すぐに失敗しなければなりません。したがって、内に存在することは決してありません(そうでなければ、JVMのバグが発生します)確認する必要はありません。コンパイル時の定数のすべてのインスタンスでメモリを予約する意味がないため、実際の修正ではフィールドをに変更する必要があります。次に、かどうかは無関係です。cnulltest()thisnulltaxosstatictest()static
ホルガー

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この値を返すための操作を実行できるからです。

barfinalキーワードを削除するか、宣言内ではなくコンストラクター内で初期化しないことによって)非コンパイル時定数に変更すると、次のようになります。

/**
 * 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_0thisメンバー関数の場合の参照)が論理的にはなり得ないことに気付くほど賢いnullです。

今あなたのケースは実際にコンパイラの最適化が欠けていますか?

@maaartinusの回答を参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.