关于效率:.filter(Optional::isPresent).map(Optional::get)不是比.flatmap(Optional::stream)更好吗?

Optional::stream如果存在,则返回一个包含该值的 Stream,否则返回一个空流。所以对于一个Stream<Optional<T>> optionals

optionals.flatMap(Optional::stream)

返回一个Stream<T>包含所有选项的当前值。但是关于它背后的功能,我不确定为每个当前值创建一个自己的流然后 flatMapping 流的流的效率有多高。

但即使在文档中,这也被称为预期用途。

与首先过滤所有当前值然后将选项映射到它们的值相比,为什么流式传输选项流不是非常低效?

回答

我很好奇并使用JMH编写了一个简单的微基准测试。

事实证明,使用flatMap(Optional::stream)over 会导致相当严重的性能损失filter(Optional::isPresent).map(Optional::get)

引入的 Java 16mapMultiflatMap用法类似,并且具有非常接近filter/ 的性能特征map

我的每个基准方法都采用一个列表Optional<Integer>并计算所有现值的总和。

我实施了三种方法:

  1. flatMap 正如提出的问题
  2. filtermap如问题中所述
  3. mapMulti 在 JDK 16 中引入。

请注意,我并没有利用的flatMapToIntmapMultiToInt方法,这或许是更有效,因为我不想把重点放在流,过包装对象方面,只是比较流的使用过Optional的对象。

对于所有方法,我使用完整列表(所有值都存在)、半空列表(每隔一个值存在)和完全空列表(每个可选值都为空)来运行基准测试。列表的长度都相同(每个列表任意选取 10 000 个元素)。

值的单位是 us/op(每个操作的微秒,意味着一个完整的流评估)。

方法 完整列表 半空列表 空列表
flatMap 207.219±1.176 175.355±4.955 142.986±2.821
filter/map 12.856±0.375 12.086±0.451 6.856±0.143
mapMulti 13.990±0.353 11.685±0.276 7.034±0.199

请注意,这里的绝对数字特定于我运行 JDK 16 的机器,无论如何都无关紧要。相对差异在这里很重要。

似乎这种flatMap方法明显更慢,而且变化更大。如果我不得不猜测可变性来自所有Stream创建的对象引起的 GC 压力增加,即使对于空结果也是如此。

免责声明:这显然只是一个正在测试的虚构示例,并且基准测试尚未经过同行评审(尚未),因此不要在没有进一步调查的情况下将这些结果视为理所当然。

下面是完整的基准代码(请注意,我拒绝了一些迭代/运行时间以在合理的时间内获得响应并硬编码为使用 4 个线程。根据需要进行调整。)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Fork(value = 1, warmups = 0)
@Warmup(iterations = 5, time = 5)
@Measurement(iterations = 5, time = 5)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(4)
public class MyBenchmark {

    @State(Scope.Benchmark)
    public static class MyLists {
        private static final int LIST_SIZE = 10_000;

        public final List<Optional<Integer>> allValues;
        public final List<Optional<Integer>> halfEmpty;
        public final List<Optional<Integer>> allEmpty;

        public MyLists() {
            List<Optional<Integer>> allValues = new ArrayList<>(LIST_SIZE);
            List<Optional<Integer>> halfEmpty = new ArrayList<>(LIST_SIZE);
            List<Optional<Integer>> allEmpty = new ArrayList<>(LIST_SIZE);
            for (int i = 0; i < LIST_SIZE; i++) {
                Optional<Integer> o = Optional.of(i);
                allValues.add(o);
                halfEmpty.add(i % 2 == 0 ? o : Optional.empty());
                allEmpty.add(Optional.empty());
            }
            this.allValues = Collections.unmodifiableList(allValues);
            this.halfEmpty = Collections.unmodifiableList(halfEmpty);
            this.allEmpty = Collections.unmodifiableList(allEmpty);
        }
    }

    @Benchmark
    public long filter_and_map_allValues(MyLists lists) {
        return filterAndMap(lists.allValues);
    }

    @Benchmark
    public long filter_and_map_halfEmpty(MyLists lists) {
        return filterAndMap(lists.halfEmpty);
    }

    @Benchmark
    public long filter_and_map_allEmpty(MyLists lists) {
        return filterAndMap(lists.allEmpty);
    }

    @Benchmark
    public long flatMap_allValues(MyLists lists) {
        return flatMap(lists.allValues);
    }

    @Benchmark
    public long flatMap_halfEmpty(MyLists lists) {
        return flatMap(lists.halfEmpty);
    }

    @Benchmark
    public long flatMap_allEmpty(MyLists lists) {
        return flatMap(lists.allEmpty);
    }


    @Benchmark
    public long mapMulti_allValues(MyLists lists) {
        return mapMulti(lists.allValues);
    }

    @Benchmark
    public long mapMulti_halfEmpty(MyLists lists) {
        return mapMulti(lists.halfEmpty);
    }

    @Benchmark
    public long mapMulti_allEmpty(MyLists lists) {
        return mapMulti(lists.allEmpty);
    }

    private long filterAndMap(List<Optional<Integer>> input) {
        return input.stream().filter(Optional::isPresent).map(Optional::get).mapToInt(Integer::intValue).sum();
    }

    private long flatMap(List<Optional<Integer>> input) {
        return input.stream().flatMap(Optional::stream).mapToInt(Integer::intValue).sum();
    }

    private long mapMulti(List<Optional<Integer>> input) {
        // Unfortunately the type witness <Integer> is necessary here, as type inference would otherwise make mapMulti produce a Stream<Object>.
        return input.stream().<Integer>mapMulti(Optional::ifPresent).mapToInt(Integer::intValue).sum();
    }
}

  • With JDK 16, a new variant is possible, [`mapMulti(Optional::ifPresent)`](https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/stream/Stream.html#mapMulti(java.util.function.BiConsumer))

以上是关于效率:.filter(Optional::isPresent).map(Optional::get)不是比.flatmap(Optional::stream)更好吗?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>