Оператор остатка на 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% 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), я получаю более разумный результат:

    public getParent (I) Я бросаю Java / IO / IOException 
       L0
        ЛИНИУМЕР 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        ЛЕНЕНУМЕР 78 Л

Так что это более логично.

RobAu
источник
@Lino уверен, но это не очень актуально для строки 70 с причинамиINVOKESTATIC
RobAu
Какой компилятор вы используете? Нормальный javacне генерирует это.
Апангин
Какой компилятор вы используете? Версия Java, Openjdk / Oracle / и т.д. Редактировать: блин, @apangin был быстрее, извините
lugiorgi
1
Он скомпилирован из Intellij 2019.3, с Java 11, openjdk version "11.0.6" 2020-01-14на Ubuntu 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не статичным, проверка должна быть сделана. Обычно это делается неявно, когда к полю обращаются или когда вызывается нестатический метод, но вы этого не делаете. Так что нужна явная проверка. Одна возможность - позвонить Objects.requireNonNull.

Байт-код

Не забывайте, что байт-код в основном не имеет отношения к производительности. Задача javacсостоит в том, чтобы создать некоторый байт-код, выполнение которого соответствует вашему исходному коду. Он не предназначен для какой-либо оптимизации, поскольку оптимизированный код обычно длиннее и сложнее анализировать, в то время как байт-код фактически является исходным кодом для оптимизирующего JIT-компилятора. Так что, javacкак ожидается, будет проще ...

Производительность

В моем профилировщике я увидел, что в процессоре тратится 1% java.util.Objects.requireNonNull

Я бы сначала обвинил профилировщика. Профилирование Java довольно сложно, и вы никогда не можете ожидать идеальных результатов.

Возможно, вам следует попробовать сделать метод статичным. Вы обязательно должны прочитать эту статью о нулевых проверках .

maaartinus
источник
1
Спасибо @maaartinus за ваш проницательный ответ. Я обязательно прочитаю вашу связанную статью.
RobAu
1
«Поскольку тест не является статичным, проверка должна быть выполнена». На самом деле, нет причин проверять, thisявляется ли тест нестатичным null. Как вы сказали сами, вызов like c.test()должен завершиться с ошибкой, когда он cесть, nullи он должен немедленно завершиться с ошибкой, вместо того, чтобы войти в метод. Так что внутри test(), thisникогда не может быть null(иначе была бы ошибка JVM). Так что проверять не нужно. Фактическим исправлением должно быть изменение поля taxosна static, так как нет смысла резервировать память в каждом случае для константы времени компиляции. Тогда не имеет значения , test()является ли это 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
RobAu
источник
1
Может ли компилятор бояться этих thisссылок null? Будет ли это возможно?
Аталант
1
Нет, это не имеет никакого смысла, разве что компилятор как-то компилирует поле Integer, а это результат автобокса?
RobAu
1
Не ALOAD 0ссылается this? Так что будет иметь смысл (не совсем), что компилятор добавляет проверку нуля
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.

atalantus
источник