Java Stream.concat VS Collection.addAll的性能

时间:2017-01-12 20:07:38

标签: java java-8 java-stream

用于在流中组合两组数据。

Stream.concat(stream1, stream2).collect(Collectors.toSet());

stream1.collect(Collectors.toSet())
       .addAll(stream2.collect(Collectors.toSet()));

哪个更有效率,为什么?

6 个答案:

答案 0 :(得分:8)

为了便于阅读和使用,Stream.concat(a, b).collect(toSet())比第二种选择更清晰。

为了问题,“什么是最有效的”,这里是一个JMH测试(我想说我不使用JMH那么多,有可能有一些空间来改善我的基准测试):

使用JMH,使用以下代码:

package stackoverflow;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Fork(1)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode({ Mode.AverageTime})
public class StreamBenchmark {
  private Set<String> s1;
  private Set<String> s2;

  @Setup
  public void setUp() {
    final Set<String> valuesForA = new HashSet<>();
    final Set<String> valuesForB = new HashSet<>();
    for (int i = 0; i < 1000; ++i) {
      valuesForA.add(Integer.toString(i));
      valuesForB.add(Integer.toString(1000 + i));
    }
    s1 = valuesForA;
    s2 = valuesForB;
  }

  @Benchmark
  public void stream_concat_then_collect_using_toSet(final Blackhole blackhole) {
    final Set<String> set = Stream.concat(s1.stream(), s2.stream()).collect(Collectors.toSet());
    blackhole.consume(set);
  }

  @Benchmark
  public void s1_collect_using_toSet_then_addAll_using_toSet(final Blackhole blackhole) {
    final Set<String> set = s1.stream().collect(Collectors.toSet());
    set.addAll(s2.stream().collect(Collectors.toSet()));
    blackhole.consume(set);
  }
}

你得到了这些结果(为了便于阅读,我省略了一些部分)。

Result "s1_collect_using_toSet_then_addAll_using_toSet":
  156969,172 ±(99.9%) 4463,129 ns/op [Average]
  (min, avg, max) = (152842,561, 156969,172, 161444,532), stdev = 2952,084
  CI (99.9%): [152506,043, 161432,301] (assumes normal distribution)

Result "stream_concat_then_collect_using_toSet":
  104254,566 ±(99.9%) 4318,123 ns/op [Average]
  (min, avg, max) = (102086,234, 104254,566, 111731,085), stdev = 2856,171
  CI (99.9%): [99936,443, 108572,689] (assumes normal distribution)
# Run complete. Total time: 00:00:25

Benchmark                                                       Mode  Cnt       Score      Error  Units
StreamBenchmark.s1_collect_using_toSet_then_addAll_using_toSet  avgt   10  156969,172 ± 4463,129  ns/op
StreamBenchmark.stream_concat_then_collect_using_toSet          avgt   10  104254,566 ± 4318,123  ns/op

使用Stream.concat(a, b).collect(toSet())的版本应该更快(如果我读好JMH数字)。

另一方面,我认为这个结果是正常的,因为你没有创建一个中间集(这有一些成本,即使是HashSet),并且如第一个回答的评论中所述,{{ 1}} lazily concatenated

使用分析器,您可能会看到哪个部分速度较慢。您可能还想使用Stream而不是toCollection(() -> new HashSet(1000))来查看问题是否在于增加toSet()内部哈希数组。

答案 1 :(得分:4)

您的问题称为premature optimization。永远不要选择一种语法,因为你认为它更快。始终使用最能表达您意图的语法,并支持理解您的逻辑。

  

你对我正在进行的任务一无所知 - alan7678

这是真的。

但我不需要。

有两种常见方案:

  1. 您开发了OLTP个应用程序。在这种情况下,应用程序应在一秒或更短的时间内响应。用户不会遇到您提供的变体之间的性能差异。

  2. 你开发了某种batch processing,它会在无人看管的情况下运行一段时间。在这种情况下,性能差异“可能”很重要,但只有在您的批处理过程运行时才收取费用。

  3. 无论哪种方式: 真正的性能问题(通过倍数而不是分数来加速应用程序)通常是由您实现的逻辑引起的(例如:过度通信,“隐藏循环”或过多的对象创建)。
    通过选择某种语法通常无法解决或阻止这些问题。

    如果省略性能增益的可读性,则会使应用程序难以维护 并且改变难以维护的代码库容易消耗可以节省的多金钱,因为在应用程序的生命周期中程序通过使用较不可读但稍快的语法来提高速度。

      毫无疑问,这个问题在某些情况下对其他人也很重要。 - alan7678

    毫无疑问,人们很好奇。

      

    幸运的是,我喜欢的语法似乎也表现得更好。 - alan7678

    如果你知道,你为什么要问?

    您是否愿意与您的测量设置分享您的测量结果?

    更重要的是:这对Java9或Java10有效吗?

    Javas性能基本上来自JVM实现,这可能会发生变化。因为新的Java版本将带来性能提升的新语法结构(作为java流)有更好的机会。但是没有保证...

      

    在我看来,对性能的需求大于可读性的差异。 - alan7678

    您是否仍会在5年内对此申请负责? 或者你是一名顾问,为了一个项目的开始,然后切换到下一个项目?

    我从来没有一个项目可以在语法级别解决我的性能问题 但是我经常使用存在10年以上的遗留代码,这很难维护,因为有人不尊重可读性。

      

    所以你的不答案不适用于我。 - alan7678

    这是一个自由的世界,请你选择。

答案 2 :(得分:4)

首先,必须强调第二个变体不正确toSet()收藏家返回Set “no guarantees on the type, mutability, serializability, or thread-safety”。如果无法保证可变性,则在生成的addAll上调用Set是不正确的。

它恰好与当前版本的参考实现一起使用,其中将创建HashSet,但可能会在将来的版本或替代实现中停止工作。要解决此问题,您必须将toSet()替换为toCollection(HashSet::new),以进行第一次Stream的collect操作。

这导致第二种变体不仅在当前实现方面效率较低,如this answer所示,它还可能会阻止将来对toSet()收集器进行优化,方法是坚持结果是确切类型HashSet。此外,与toSet()收集器不同,toCollection(…)收集器无法检测到目标集合是否无序,这可能在将来的实现中具有性能相关性。

答案 3 :(得分:1)

没有基准测试是不可能告诉前面的,但要考虑一下:如果有很多重复项,那么Stream.concat(stream1, stream2)必须创建一个必须实例化的大对象,因为你是callig .collect()。 / p>

然后.toSet()必须将每个出现的事件与之前的每个事件进行比较,可能是使用快速哈希函数,但仍然可能有很多元素。

另一方面,stream1.collect(Collectors.toSet()) .addAll(stream2.collect(Collectors.toSet()))将创建两个较小的集合,然后合并它们。

第二个选项的内存占用量可能小于第一个选项。

修改

我在阅读@NoDataFound基准测试后重新审视了这一点。在更复杂的测试版本中,Stream.concat确实比Collection.addAll执行速度更快。我试图考虑有多少不同的元素和初始流有多大。我还从测量中取出了从集合创建输入流所需的时间(无论如何都可以忽略不计)。以下是我使用下面代码获得的时间示例。

Concat-collect   10000 elements, all distinct: 7205462 nanos
Collect-addAll   10000 elements, all distinct: 12130107 nanos

Concat-collect  100000 elements, all distinct: 78184055 nanos
Collect-addAll  100000 elements, all distinct: 115191392 nanos

Concat-collect 1000000 elements, all distinct: 555265307 nanos
Collect-addAll 1000000 elements, all distinct: 1370210449 nanos

Concat-collect 5000000 elements, all distinct: 9905958478 nanos
Collect-addAll 5000000 elements, all distinct: 27658964935 nanos

Concat-collect   10000 elements, 50% distinct: 3242675 nanos
Collect-addAll   10000 elements, 50% distinct: 5088973 nanos

Concat-collect  100000 elements, 50% distinct: 389537724 nanos
Collect-addAll  100000 elements, 50% distinct: 48777589 nanos

Concat-collect 1000000 elements, 50% distinct: 427842288 nanos
Collect-addAll 1000000 elements, 50% distinct: 1009179744 nanos

Concat-collect 5000000 elements, 50% distinct: 3317183292 nanos
Collect-addAll 5000000 elements, 50% distinct: 4306235069 nanos

Concat-collect   10000 elements, 10% distinct: 2310440 nanos
Collect-addAll   10000 elements, 10% distinct: 2915999 nanos

Concat-collect  100000 elements, 10% distinct: 68601002 nanos
Collect-addAll  100000 elements, 10% distinct: 40163898 nanos

Concat-collect 1000000 elements, 10% distinct: 315481571 nanos
Collect-addAll 1000000 elements, 10% distinct: 494875870 nanos

Concat-collect 5000000 elements, 10% distinct: 1766480800 nanos
Collect-addAll 5000000 elements, 10% distinct: 2721430964 nanos

Concat-collect   10000 elements,  1% distinct: 2097922 nanos
Collect-addAll   10000 elements,  1% distinct: 2086072 nanos

Concat-collect  100000 elements,  1% distinct: 32300739 nanos
Collect-addAll  100000 elements,  1% distinct: 32773570 nanos

Concat-collect 1000000 elements,  1% distinct: 382380451 nanos
Collect-addAll 1000000 elements,  1% distinct: 514534562 nanos

Concat-collect 5000000 elements,  1% distinct: 2468393302 nanos
Collect-addAll 5000000 elements,  1% distinct: 6619280189 nanos

代码

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamBenchmark {
    private Set<String> s1;
    private Set<String> s2;

    private long createStreamsTime;
    private long concatCollectTime;
    private long collectAddAllTime;

    public void setUp(final int howMany, final int distinct) {
        final Set<String> valuesForA = new HashSet<>(howMany);
        final Set<String> valuesForB = new HashSet<>(howMany);
        if (-1 == distinct) {
            for (int i = 0; i < howMany; ++i) {
                valuesForA.add(Integer.toString(i));
                valuesForB.add(Integer.toString(howMany + i));
            }
        } else {
            Random r = new Random();
            for (int i = 0; i < howMany; ++i) {
                int j = r.nextInt(distinct);
                valuesForA.add(Integer.toString(i));
                valuesForB.add(Integer.toString(distinct + j));
            }
        }
        s1 = valuesForA;
        s2 = valuesForB;
    }

    public void run(final int streamLength, final int distinctElements, final int times, boolean discard) {
        long startTime;
        setUp(streamLength, distinctElements);
        createStreamsTime = 0l;
        concatCollectTime = 0l;
        collectAddAllTime = 0l;
        for (int r = 0; r < times; r++) {
            startTime = System.nanoTime();
            Stream<String> st1 = s1.stream();
            Stream<String> st2 = s2.stream();
            createStreamsTime += System.nanoTime() - startTime;
            startTime = System.nanoTime();
            Set<String> set1 = Stream.concat(st1, st2).collect(Collectors.toSet());
            concatCollectTime += System.nanoTime() - startTime;
            st1 = s1.stream();
            st2 = s2.stream();
            startTime = System.nanoTime();
            Set<String> set2 = st1.collect(Collectors.toSet());
            set2.addAll(st2.collect(Collectors.toSet()));
            collectAddAllTime += System.nanoTime() - startTime;
        }
        if (!discard) {
            // System.out.println("Create streams "+streamLength+" elements,
            // "+distinctElements+" distinct: "+createStreamsTime+" nanos");
            System.out.println("Concat-collect " + streamLength + " elements, " + (distinctElements == -1 ? "all" : String.valueOf(100 * distinctElements / streamLength) + "%") + " distinct: " + concatCollectTime + " nanos");
            System.out.println("Collect-addAll " + streamLength + " elements, " + (distinctElements == -1 ? "all" : String.valueOf(100 * distinctElements / streamLength) + "%") + " distinct: " + collectAddAllTime + " nanos");
            System.out.println("");
        }
    }

    public static void main(String args[]) {
        StreamBenchmark test = new StreamBenchmark();
        final int times = 5;
        test.run(100000, -1, 1, true);
        test.run(10000, -1, times, false);
        test.run(100000, -1, times, false);
        test.run(1000000, -1, times, false);
        test.run(5000000, -1, times, false);
        test.run(10000, 5000, times, false);
        test.run(100000, 50000, times, false);
        test.run(1000000, 500000, times, false);
        test.run(5000000, 2500000, times, false);
        test.run(10000, 1000, times, false);
        test.run(100000, 10000, times, false);
        test.run(1000000, 100000, times, false);
        test.run(5000000, 500000, times, false);
        test.run(10000, 100, times, false);
        test.run(100000, 1000, times, false);
        test.run(1000000, 10000, times, false);
        test.run(5000000, 50000, times, false);
    }
}

答案 4 :(得分:1)

使用其中之一。

如果您对自己的应用进行了分析,并且此部分代码是瓶颈,那么请考虑使用不同的实现分析您的应用并使用效果最好的应用

答案 5 :(得分:1)

我一直在决定是否将Stream.of()与flatMap()或Stream.concat()或Collections.addAll()或Collections.add()结合使用,以将多个列表合并为一个列表。我用10次迭代对代码进行了快速线束测试,并获得了一些令人惊讶的结果。

------------------------------------------------------------------
1. Using getByAddAll()

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.414 ± 0.304  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.291 ± 0.332  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.571 ± 0.622  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.520 ± 0.818  ms/op

Average = 4.449ms
------------------------------------------------------------------

------------------------------------------------------------------
2. Using getByAdd()

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.280 ± 0.499  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.494 ± 0.374  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.575 ± 0.539  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.552 ± 0.272  ms/op

Average = 4.475ms
------------------------------------------------------------------


------------------------------------------------------------------
3. using getByStreamOf()
Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.502 ± 0.529  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.494 ± 0.754  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.676 ± 0.347  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.511 ± 0.950  ms/op

Average = 4.545ms
------------------------------------------------------------------


------------------------------------------------------------------
4. Using getByStreamConcat()

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.342 ± 0.372  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.218 ± 0.400  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.892 ± 0.562  ms/op

Benchmark                         Mode  Cnt  Score   Error  Units
PerformaceTest.test               avgt   10  4.818 ± 0.608  ms/op

Average = 4.567ms
------------------------------------------------------------------

这是我的代码

private List<ItemDTO> getByStreamOf(OfferResponseDTO catalogOfferDTO){
    return Stream.of(
            catalogOfferDTO.getCharges()
                    .stream()
                    .map(chargeWithPricePlanResponseDTO -> new ItemDTO(chargeWithPricePlanResponseDTO.getName(), catalogOfferDTO.getDisplayOrder())),

            catalogOfferDTO.getUsages()
                    .stream()
                    .map(usageResponseDTO -> new ItemDTO(usageResponseDTO.getDescription(), catalogOfferDTO.getDisplayOrder())),

            catalogOfferDTO.getNetworkElements()
                    .stream()
                    .map(networkElementResponseDTO -> new ItemDTO(networkElementResponseDTO.getName(), catalogOfferDTO.getDisplayOrder())),

            catalogOfferDTO.getEquipment()
                    .stream()
                    .map(equipmentResponseDTO -> new ItemDTO(equipmentResponseDTO.getInvoiceDescription(), catalogOfferDTO.getDisplayOrder())))

            .flatMap(Function.identity())
            .collect(Collectors.toList());
}


private List<ItemDTO> getByStreamConcat(OfferResponseDTO catalogOfferDTO){
    return Stream.concat(
            Stream.concat(
                    catalogOfferDTO.getCharges()
                            .stream()
                            .map(chargeWithPricePlanResponseDTO -> new ItemDTO(chargeWithPricePlanResponseDTO.getName(), catalogOfferDTO.getDisplayOrder()))
                    ,

                    catalogOfferDTO.getUsages()
                            .stream()
                            .map(usageResponseDTO -> new ItemDTO(usageResponseDTO.getDescription(),catalogOfferDTO.getDisplayOrder()))
            ),
            Stream.concat(
                    catalogOfferDTO.getEquipment()
                            .stream()
                            .map(equipmentResponseDTO -> new ItemDTO(equipmentResponseDTO.getInvoiceDescription(), catalogOfferDTO.getDisplayOrder())),

                    catalogOfferDTO.getNetworkElements()
                            .stream()
                            .map(networkElementResponseDTO -> new ItemDTO(networkElementResponseDTO.getName(), catalogOfferDTO.getDisplayOrder()))
            )
    )
            .collect(Collectors.toList());
}


private List<ItemDTO> getByAddAll(OfferResponseDTO catalogOfferDTO){
    List<ItemDTO> items = new ArrayList<>();

    items.addAll(catalogOfferDTO.getCharges()
            .stream()
            .map(chargeWithPricePlanResponseDTO -> new ItemDTO(chargeWithPricePlanResponseDTO.getName(), catalogOfferDTO.getDisplayOrder()))
            .collect(Collectors.toList()));

    items.addAll(catalogOfferDTO.getUsages()
            .stream()
            .map(usageResponseDTO -> new ItemDTO(usageResponseDTO.getDescription(), catalogOfferDTO.getDisplayOrder()))
            .collect(Collectors.toList()));

    items.addAll(catalogOfferDTO.getNetworkElements()
            .stream()
            .map(networkElementResponseDTO -> new ItemDTO(networkElementResponseDTO.getName(), catalogOfferDTO.getDisplayOrder()))
            .collect(Collectors.toList()));

    items.addAll(catalogOfferDTO.getEquipment()
            .stream()
            .map(equipmentResponseDTO -> new ItemDTO(equipmentResponseDTO.getInvoiceDescription(), catalogOfferDTO.getDisplayOrder()))
            .collect(Collectors.toList()));
    return items;
}

private List<ItemDTO> getByAdd(OfferResponseDTO catalogOfferDTO){
    List<ItemDTO> items = new ArrayList<>();

    catalogOfferDTO.getCharges()
            .stream()
            .map(chargeWithPricePlanResponseDTO -> items.add(this.addItem(chargeWithPricePlanResponseDTO.getName(), catalogOfferDTO.getDisplayOrder())));

    catalogOfferDTO.getUsages()
            .stream()
            .map(usageResponseDTO -> items.add(this.addItem(usageResponseDTO.getDescription(), catalogOfferDTO.getDisplayOrder())));

    catalogOfferDTO.getEquipment()
            .stream()
            .map(equipmentResponseDTO -> items.add(this.addItem(equipmentResponseDTO.getInvoiceDescription(), catalogOfferDTO.getDisplayOrder())));

    catalogOfferDTO.getNetworkElements()
            .stream()
            .map(networkElementResponseDTO -> items.add(this.addItem(networkElementResponseDTO.getName(), catalogOfferDTO.getDisplayOrder())));

    return items;
}