2017-05-26 62 views
1

有人可以向我解释为什么下面的代码在流版本中比在一个版本中更快,而不仅仅是返回主列表的子列表的过滤器?为什么要过滤一个流比仅仅返回一个子列表的迭代代码更快?

public static final int N = 50000000; 

static List<Integer> sourceList = new ArrayList<>(); 

static { 
    for (int i = 0; i < N; i++) { 
    sourceList.add(i); 
    } 
} 

@Benchmark 
public List<Pair<Integer, Integer>> vanilla() { 
    List<Pair<Integer, Integer>> collect1 = 
    sourceList.stream() 
    .map(integer -> Pair.of(integer, integer)) 
    .collect(Collectors.toList()); 
    return collect1.subList(1000, 100000); 
} 

@Benchmark 
public List<Pair<Integer, Integer>> stream() { 
    return sourceList.stream() 
    .map(value -> Pair.of(value, value)) 
    .filter(value -> value.getLeft() > 1000 && value.getLeft() < 100000) 
    .collect(Collectors.toList()); 
} 

Benchmark  Mode Cnt Score Error Units 
Test.stream avgt 20 9.867 ± 0.218 ns/op 
Test.vanilla avgt 20 183.304 ± 8.550 ns/op 

我使用JMH运行测试,我不明白结果。我想通过添加将Integer值封装在Pair中的映射函数会强制流创建所有新对象,以将它们传递给过滤器方法,然后提取Pair的左边部分进行比较。对于我来说,这听起来比另一种没有过滤的方法更加密集,结果只是原来的一个子列表,因此没有遍历整个列表。

我失去了一些东西在这里?

回答

2

很可能是因为第一个必须填写50000000个元素的整个列表,这涉及分配更多的内存,每次达到容量时都会为列表使用内部数组的副本。

第二个只需创建99000个元素的列表,从而分配更少的内存,并减少内部数组的副本。

甚至更​​快的解决方案是在映射之前进行过滤,从而避免无用的装箱和创建对。限制到100,000人当然也会更快。

+0

好的,这是有道理的。我知道先过滤会更快,但我试图简化此示例的生产代码,该代码也首先进行一些映射。生产代码的确如此,它必须按照它们来的顺序为每个对象分配一个int值,然后只保留那些给定下限和上限的值。问题是我需要保持一个计数器,并将其传递给过滤器,因为对象不是像这样的int。是否有任何方法可以重构此示例代码以保持语义,同时反转映射和过滤功能? – Kilian

+1

在你发布的例子中首先进行过滤将是微不足道的,对吗?对于您的实际生产代码,我不能没有看到它。 –

0

性能问题不是subList的问题,它确实简单地包装了主要List

ArrayList将重置容量&将元素复制到新的数组中重复需要添加更多元素。 vanilla使用更大的内存大小来添加50000000元素。所以它比stream慢,因为stream仅添加[1000..100000]元素。

以下部分是ArrayList类需要扩展容量,更多元素添加到ArrayList结果更ensureCapacityInternal方法调用:

private void ensureCapacityInternal(int minCapacity) { 
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
     minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
    } 

    ensureExplicitCapacity(minCapacity); 
} 

也许以下版本vanilla会比stream快,但仍然使用更大的内存大小:

@Benchmark 
public List<Pair<Integer, Integer>> vanilla() { 
    List<Pair<Integer, Integer>> main = 
    sourceList.stream() 
      .map(integer -> Pair.of(integer, integer)) 
      .collect(Collectors.toCollection(()->new ArrayList<>(N))); 
    return main.subList(1000, 100000); 
} 
+1

stream()方法创建与另一个Pair对象一样多的Pair对象。但它不会将它们全部添加到列表中。 –

+0

@JBNizet对不起,只是输入错误。 –

相关问题