Der verbleibende Operator auf int verursacht java.util.Objects.requireNonNull?

12

Ich versuche, mit einer internen Methode so viel Leistung wie möglich zu erzielen.

Der Java-Code lautet:

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
}

In meinem Profiler habe ich gesehen, dass 1% der CPU-Ausgaben anfallen java.util.Objects.requireNonNull, aber ich nenne das nicht einmal. Bei der Überprüfung des Bytecodes habe ich Folgendes gesehen:

 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

Der Compiler generiert also diese (nutzlose?) Prüfung. Ich arbeite an nullGrundelementen, die sowieso nicht sein können. Warum generiert der Compiler diese Zeile? Ist es ein Fehler? Oder "normales" Verhalten?

(Ich könnte mit einer Bitmaske herumarbeiten, aber ich bin nur neugierig)

[AKTUALISIEREN]

  1. Der Bediener scheint nichts damit zu tun zu haben (siehe Antwort unten)

  2. Mit dem Eclipse-Compiler (Version 4.10) erhalte ich dieses vernünftigere Ergebnis:

    public getParent (I) Ich löse java / io / IOException 
       L0
        LEINENUMMER 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        LEINENUMBER 78 L.

Das ist also logischer.

RobAu
quelle
@Lino sicher, aber das ist nicht wirklich relevant für Zeile 70 mit Ursachen derINVOKESTATIC
RobAu
Welchen Compiler verwenden Sie? Normal javacerzeugt dies nicht.
Apangin
Welchen Compiler verwenden Sie? Java-Version, Openjdk / Oracle / etc. Edit: whops, @apangin war schneller, sorry
lugiorgi
1
Es wurde aus Intellij 2019.3 mit Java 11 openjdk version "11.0.6" 2020-01-14auf Ubuntu 64 Bit kompiliert .
RobAu

Antworten:

3

Warum nicht?

Vorausgesetzt

class C {
    private final int taxos = 4;

    public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }
}

Ein Anruf wie c.test()where cwird als C muss geworfen, wenn er cist null. Ihre Methode entspricht

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

da Sie nur mit Konstanten arbeiten. Da testes nicht statisch ist, muss die Überprüfung durchgeführt werden. Normalerweise wird dies implizit ausgeführt, wenn auf ein Feld zugegriffen wird oder eine nicht statische Methode aufgerufen wird, aber Sie tun dies nicht. Daher ist eine explizite Überprüfung erforderlich. Eine Möglichkeit ist anzurufen Objects.requireNonNull.

Der Bytecode

Vergessen Sie nicht, dass der Bytecode für die Leistung grundsätzlich irrelevant ist. Die Aufgabe von javacbesteht darin, einen Bytecode zu erzeugen, dessen Ausführung Ihrem Quellcode entspricht. Es ist nicht zu tun , bedeutet keine Optimierungen, wie optimierte Code in der Regel länger und schwieriger zu analysieren, während die Bytecode ist eigentlich der Quellcode für die Optimierung JIT - Compiler. Es javacwird also erwartet, dass es einfach bleibt ....

Die Performance

In meinem Profiler habe ich gesehen, dass 1% der CPU-Ausgaben in sind java.util.Objects.requireNonNull

Ich würde zuerst den Profiler beschuldigen. Das Profilieren von Java ist ziemlich schwierig und Sie können niemals perfekte Ergebnisse erwarten.

Sie sollten wahrscheinlich versuchen, die Methode statisch zu machen. Sie sollten diesen Artikel über Nullprüfungen lesen .

Maaartinus
quelle
1
Vielen Dank an @maaartinus für Ihre aufschlussreiche Antwort. Ich werde sicherlich Ihren verlinkten Artikel lesen.
RobAu
1
„Da der Test nicht statisch ist, muss die Prüfung durchgeführt werden.“ Tatsächlich gibt es keinen Grund zu testen, ob der Test nicht statisch thisist null. Wie Sie selbst sagten, muss ein Aufruf wie c.test()fehlschlagen, wenn er cist, nullund er muss sofort fehlschlagen, anstatt die Methode einzugeben. Also innerhalb test(), thiskann nie sein null(sonst gäbe es einen JVM Fehler sein). Also keine Notwendigkeit zu überprüfen. Der eigentliche Fix sollte das Feld taxosin ändern static, da es keinen Sinn macht, in jedem Fall Speicher für eine Konstante zur Kompilierungszeit zu reservieren. Dann test()ist statices irrelevant , ob es irrelevant ist.
Holger
2

Nun, es scheint, meine Frage war "falsch", da sie nichts mit dem Operator zu tun hat, sondern mit dem Feld selbst. Ich weiß immer noch nicht warum ..

   public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }

Was wird in:

  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
RobAu
quelle
1
Könnte der Compiler tatsächlich Angst haben, dass thisReferenzen null? Wäre das möglich?
Atalantus
1
Nein, das macht keinen Sinn, es sei denn, der Compiler kompiliert das Feld Integerirgendwie und dies ist das Ergebnis von Autoboxen?
RobAu
1
Nicht ALOAD 0verweisen this? Es wäre also sinnvoll (nicht wirklich), dass der Compiler einen Nullcheck hinzufügt
Lino
1
Der Compiler fügt also tatsächlich eine Nullprüfung für hinzu this? Großartig: /
RobAu
1
Ich werde versuchen, mit der Befehlszeile einen minimalen Code zu erstellen javac, um ihn morgen zu überprüfen. und wenn das auch dieses verhalten zeigt, denke ich, dass es ein javac-bug sein könnte?
RobAu
2

Zunächst ein minimal reproduzierbares Beispiel für dieses Verhalten:

/**
 * 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;
    }
}

Das Verhalten beruht darauf, wie der Java-Compiler Konstanten zur Kompilierungszeit optimiert .

Beachten Sie, dass im Bytecode foo()keine Objektreferenz aufgerufen wird, um den Wert von zu erhalten bar. iconst_5Dies liegt daran, dass es sich um eine Konstante zur Kompilierungszeit handelt und die JVM die Operation einfach ausführen kann , um diesen Wert zurückzugeben.

Wenn Sie barin eine nicht kompilierte Zeitkonstante wechseln (entweder indem Sie das finalSchlüsselwort entfernen oder nicht innerhalb der Deklaration, sondern innerhalb des Konstruktors initialisieren), erhalten Sie:

/**
 * 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;
    }
}

Dabei wird aload_0die Referenz von thisauf den Operandenstapel verschoben, um dann das barFeld dieses Objekts abzurufen.

Hier ist der Compiler klug genug zu bemerken, dass aload_0(die thisReferenz bei Mitgliedsfunktionen) logischerweise nicht sein kann null.

Ist Ihr Fall tatsächlich eine fehlende Compileroptimierung?

Siehe @ maaartinus Antwort.

Atalantus
quelle