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

Я просто наткнулся на необязательный класс в Java 8 - мне очень нравится подход замены некоторых нулевых проверок (что буквально означает "присутствует значение?" ) в моем коде с вызовами метода isPresent().

Мой вопрос: не приведет ли к снижению производительности моего кода? Я просто догадываюсь, что простые проверки на null могут быть немного дешевле, и я не очень хорошо разбираюсь в чтении/интерпретации кода в байтах, поэтому мне действительно интересно ваши мысли по этой теме.

Ответ 1

Optional<T> является просто нормальным общим классом, который содержит ссылку типа T. Таким образом, он добавляет один слой косвенности. Метод сам по себе не будет слишком дорогостоящим, так как класс final, и поэтому можно избежать динамической отправки.

Единственное место, где у вас могут быть проблемы с производительностью, - это работа с очень большим количеством таких экземпляров, но даже тогда производительность чего-то вроде Stream<Optional<String>> неплохая. Тем не менее, при работе с большими количествами примитивных значений вы обнаружите поражение производительности с помощью Stream<Integer> (или Integer[]) по сравнению с примитивной специализацией IntStream (или int[]) из-за этого слоя косвенности, требующего очень частого создание объектов Integer. Однако это штраф, который мы уже знаем и платим при использовании таких вещей, как ArrayList<Integer>.

Очевидно, что вы столкнулись с тем же самым ударом с помощью Stream<OptionalInt>/OptionalInt[], поскольку необязательныйInt - это в основном класс с полем int и флаг boolean для присутствия (в отличие от Optional<T>, который может сделать с полем T) и, таким образом, очень похож на Integer, хотя и больше по размеру. И, конечно же, Stream<Optional<Integer>> добавит два уровня косвенности с соответствующим двойным снижением производительности.

Ответ 2

Я провел некоторое тестирование производительности, используя алгоритм, который интенсивно использует нулевые проверки, а также доступ к потенциально обнуляемому полю. Я реализовал простой алгоритм, который удаляет средний элемент из единого связанного списка.

Сначала я реализовал два класса узла связанного списка: безопасный - с дополнительным и небезопасный - без.

Безопасный узел

class Node<T> {
    private final T data;
    private Optional<Node<T>> next = Optional.empty();

    Node(T data) {

        this.data = data;
    }

    Optional<Node<T>> getNext() {
        return next;
    }

    void setNext(Node<T> next) { setNext(Optional.ofNullable(next)); }

    void setNext(Optional<Node<T>> next ) { this.next = next; }
}

Небезопасный узел

class NodeUnsafe<T> {
    private final T data;
    private NodeUnsafe<T> next;

    NodeUnsafe(T data) {
        this.data = data;
    }

    NodeUnsafe<T> getNext() {
        return next;
    }

    void setNext(NodeUnsafe<T> next) {
        this.next = next;
    }
}

Затем я реализовал два аналогичных метода с единственным отличием - первый использует Node<T> а второй - NodeUsafe<T>

class DeleteMiddle {

    private static <T> T getLinkedList(int size, Function<Integer, T> supplier, BiConsumer<T, T> reducer) {
        T head = supplier.apply(1);
        IntStream.rangeClosed(2, size).mapToObj(supplier::apply).reduce(head,(a,b)->{
            reducer.accept(a,b);
            return b;
        });
        return head;
    }

    private static void deleteMiddle(Node<Integer> head){
        Optional<Node<Integer>> oneStep = Optional.of(head);
        Optional<Node<Integer>> doubleStep = oneStep;
        Optional<Node<Integer>> prevStep = Optional.empty();

        while (doubleStep.isPresent() && doubleStep.get().getNext().isPresent()){
            doubleStep = doubleStep.get().getNext().get().getNext();
            prevStep = oneStep;
            oneStep = oneStep.get().getNext();
        }

        final Optional<Node<Integer>> toDelete = oneStep;
        prevStep.ifPresent(s->s.setNext(toDelete.flatMap(Node::getNext)));
    }

    private static void deleteMiddleUnsafe(NodeUnsafe<Integer> head){
        NodeUnsafe<Integer> oneStep = head;
        NodeUnsafe<Integer> doubleStep = oneStep;
        NodeUnsafe<Integer> prevStep = null;

        while (doubleStep != null && doubleStep.getNext() != null){
            doubleStep = doubleStep.getNext().getNext();
            prevStep = oneStep;
            oneStep = oneStep.getNext();
        }
        if (prevStep != null) {
            prevStep.setNext(oneStep.getNext());
        }
    }

    public static void main(String[] args) {
        int size = 10000000;
        Node<Integer> head = getLinkedList(size, Node::new, Node::setNext);
        Long before = System.currentTimeMillis();
        deleteMiddle(head);
        System.out.println("Safe: " +(System.currentTimeMillis() - before));

        NodeUnsafe<Integer> headUnsafe = getLinkedList(size, NodeUnsafe::new, NodeUnsafe::setNext);
        before = System.currentTimeMillis();
        deleteMiddleUnsafe(headUnsafe);
        System.out.println("Unsafe: " +(System.currentTimeMillis() - before));
    }
}

Сравнение двух нескольких прогонов с разным размером списка показывает, что подход с кодом, который в лучшем случае использует Optional в два раза медленнее, чем у кода с обнуляемыми значениями. С небольшими списками это в 3 раза медленнее.

Ответ 3

На скамейке отмечен код ниже, используя openjdk.

sc.map(MYObject::getRequest)
  .map(RequestDO::getMyInst)
  .map(MyInstDO::getCar)
  .map(CarDO::getId); 

if(id.isPresent())

ИЛИ

if( null != MYObject.getRequest() && null != 
    MYObject.getRequest().getMyInst() && null != 
    MYObject.getRequest().getMyInst().getCar() && null != 
    MYObject.getRequest().getMyInst().getCar().getId() )

И результат показывает Необязательный намного лучше, чем традиционный, но не нулевой.

Benchmark                     Mode     Cnt        Score    Error   Units

JMHBMarkModes.measureNotNull  thrpt    5          0.149    ± 0.036  ops/us
JMHBMarkModes.measureOptional thrpt    5         11.418    ± 1.140  ops/us
JMHBMarkModes.measureNotNull  avgt     5         12.342    ± 8.334  us/op
JMHBMarkModes.measureOptional avgt     5          0.088    ± 0.010  us/op

Но если ваш пример использования похож на (null != MYObject.getRequest()), то лучше проверить нуль. Таким образом, дополнительная производительность зависит от используемого варианта использования.