Оператор, що залишається на 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
        LINENUMBER 77 L0
        ІЛОАД 1
        ICONST_4
        ІРЕМ
        ISTORE 2
       L1
        LINENUMBER 78 L

Тож це більш логічно.


@Lino впевнений, але це не дуже актуально для рядка 70 із причинамиINVOKESTATIC
RobAu

Який компілятор ви використовуєте? Нормальний javacне створює цього.
apangin

Який компілятор ви використовуєте? Версія Java, Openjdk / Oracle / тощо.? Редагувати: whops, @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 досить складно, і ви ніколи не можете очікувати ідеальних результатів.

Напевно, ви повинні спробувати зробити метод статичним. Вам неодмінно варто прочитати цю статтю про нульові перевірки .


1
Дякую @maaartinus за вашу проникливу відповідь. Я обов'язково прочитаю вашу пов’язану статтю.
RobAu

1
"Якщо тест є нестатичним, перевірку потрібно зробити" Насправді, немає причин перевіряти, чи thisнемає null. Як ви самі сказали, виклик на зразок 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

1
Чи може компілятор насправді побоюватися цих thisпосилань null? Це було б можливо?
atalantus

1
Ні, це не має сенсу, якщо компілятор Integerякось не скомпонує поле , і це результат автобоксингу?
RobAu

1
Не ALOAD 0посилається this? Тож було б сенс (не дуже), що компілятор додає нульову перевірку
Lino

1
Тож компілятор фактично додає нульову перевірку this?
Чудово

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.