Производительность 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())
, то лучше проверить нуль. Таким образом, дополнительная производительность зависит от используемого варианта использования.