Оператор остатка для int вызывает java .util.Objects.requireNonNull? - PullRequest
12 голосов
/ 03 февраля 2020

Я пытаюсь получить как можно большую производительность с помощью какого-либо внутреннего метода.

Код 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)I throws java/io/IOException 
       L0
        LINENUMBER 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        LINENUMBER 78 L

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

Ответы [ 3 ]

3 голосов
/ 03 февраля 2020

Почему бы и нет?

Предполагая

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 не указано c, проверка должна быть выполнена. Обычно это делается неявно, когда к полю обращаются или когда вызывается нестатический c метод, но вы этого не делаете. Так что нужна явная проверка. Одна возможность - вызвать Objects.requireNonNull.

Байт-код

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

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

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

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

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

2 голосов
/ 03 февраля 2020

Во-первых, вот минимальный воспроизводимый пример такого поведения:

/**
 * 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.

2 голосов
/ 03 февраля 2020

Ну, похоже, мой вопрос был «неправильным», поскольку он не имеет ничего общего с оператором, а скорее с самим полем. До сих пор не знаю, почему ..

   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
...