问题 Java的性能可选


我只是偶然发现了Java 8中的Optional类 - 我真的喜欢在我的代码中使用isPresent()方法调用替换一些空检查(字面意思是“值是否存在?”)的方法。

我的问题是:这不会导致我的代码性能下降吗?我只是猜测简单的空检查可能会有点便宜,而且我在字节码读取/解释方面还不是很好,所以我真的对你对这个主题的想法很感兴趣。


6592
2018-01-09 17:52


起源

你为什么不对它进行基准测试? - Vince Emigh
你不应该这样做 isPresent而是使用 map 和 orElse。 - Łukasz
@Łukasz:这需要理由。有时这是真的,但是如果你想在值存在时进行副作用操作,那么如果没有,你会怎么做 if (isPresent()) doSomething()?地图和orElse都没有意义。 - Mark Peters
代码的性能绝不是关于空检查的速度,所以它甚至都不重要。 - Kayaman
@jod我的意思是,在现实生活中,一点都没关系(没有双关语意)。提高了正确性和可读性 Optional 除非您在非常特定的环境中工作,否则比字节码的数量要重要得多。 - Kayaman


答案:


Optional<T> 它只是一个普通的泛型类,它包含类型为T的引用。因此,它添加了一个间接层。这个方法调用自己也不会很贵,因为类是 final 因此可以避免动态调度。

唯一可能遇到性能问题的地方是在处理大量此类实例时,但即使这样,也会出现类似的情况 Stream<Optional<String>> 一点都不差。但是,在处理大量原始值时,您会发现使用的性能损失 Stream<Integer> (要么 Integer[]与原始专业化相比 IntStream (要么 int[]由于这个间接层需要非常频繁的实例化 Integer 对象。然而,这是我们已经知道并且在使用诸如此类之类的东西时付出的代价 ArrayList<Integer>

你显然会经历同样的打击 Stream<OptionalInt> / OptionalInt[]因为OptionalInt基本上是一个带有的类 int 领域和 boolean 存在的标志(不同于 Optional<T> 这只能与之相关 T 场)因此非常相似 Integer 虽然尺寸更大。当然,还有 Stream<Optional<Integer>> 会添加  间接级别,具有相应的双重性能损失。


11
2018-01-09 18:02





我使用大量使用空检查的算法以及对可能为空的字段的访问进行了一些性能测试。我实现了一个简单的算法,从单个链表中删除中间元素。

首先,我实现了两类链表节点:safe - with Optional和unsafe - without。

安全节点

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在最好的情况下,比使用nullables的情况慢两倍。使用小列表它慢3倍。


5
2018-02-11 18:31





我们使用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() )

结果显示Optional比传​​统的非null检查要好得多。

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()) ,那么不是空检查更好。所以可选性能取决于您的用例。


0
2017-12-14 21:50



正是我正在寻找的基准。但是什么意味着畏惧和avgt?分数是ms?错误是什么? - Momo
thrpt - 吞吐量测量每秒的操作数。 avgt - 平均时间 - jAvA