Java 8流与迭代器性能

Abh*_*kar 5 list microbenchmark java-8 java-stream jmh

我正在比较两种方法来过滤列表,使用和不使用流.事实证明,对于10,000个项目的列表,不使用流的方法更快.我有兴趣理解为什么会这样.有人能解释一下结果吗?

public static int countLongWordsWithoutUsingStreams(
        final List<String> words, final int longWordMinLength) {
    words.removeIf(word -> word.length() <= longWordMinLength);

    return words.size();
}

public static int countLongWordsUsingStreams(final List<String> words, final int longWordMinLength) {
    return (int) words.stream().filter(w -> w.length() > longWordMinLength).count();
}
Run Code Online (Sandbox Code Playgroud)

使用JMH的Microbenchmark:

@Benchmark
@BenchmarkMode(Throughput)
@OutputTimeUnit(MILLISECONDS)
public void benchmarkCountLongWordsWithoutUsingStreams() {
    countLongWordsWithoutUsingStreams(nCopies(10000, "IAmALongWord"), 3);
}

@Benchmark
@BenchmarkMode(Throughput)
@OutputTimeUnit(MILLISECONDS)
public void benchmarkCountLongWordsUsingStreams() {
    countLongWordsUsingStreams(nCopies(10000, "IAmALongWord"), 3);
}

public static void main(String[] args) throws RunnerException {
    final Options opts = new OptionsBuilder()
        .include(PracticeQuestionsCh8Benchmark.class.getSimpleName())
        .warmupIterations(5).measurementIterations(5).forks(1).build();

    new Runner(opts).run();
}
Run Code Online (Sandbox Code Playgroud)

java -jar target/benchmarks.jar -wi 5 -i 5 -f 1

基准
模式Cnt得分误差单位
PracticeQuestionsCh8Benchmark.benchmarkCountLongWordsUsingStreams thrpt 5 10.219 ±0.408 ops/ms
PracticeQuestionsCh8Benchmark.benchmarkCountLongWordsWithoutUsingStreams thrpt 5 910.785 ±21.215 ops/ms

编辑:(因为有人删除了作为答案发布的更新)

public class PracticeQuestionsCh8Benchmark {
    private static final int NUM_WORDS = 10000;
    private static final int LONG_WORD_MIN_LEN = 10;

    private final List<String> words = makeUpWords();

    public List<String> makeUpWords() {
        List<String> words = new ArrayList<>();
        final Random random = new Random();

        for (int i = 0; i < NUM_WORDS; i++) {
            if (random.nextBoolean()) {
                /*
                 * Do this to avoid string interning. c.f.
                 * http://en.wikipedia.org/wiki/String_interning
                 */
                words.add(String.format("%" + LONG_WORD_MIN_LEN + "s", i));
            } else {
                words.add(String.valueOf(i));
            }
        }

        return words;
    }

    @Benchmark
    @BenchmarkMode(AverageTime)
    @OutputTimeUnit(MILLISECONDS)
    public int benchmarkCountLongWordsWithoutUsingStreams() {
        return countLongWordsWithoutUsingStreams(words, LONG_WORD_MIN_LEN);
    }

    @Benchmark
    @BenchmarkMode(AverageTime)
    @OutputTimeUnit(MILLISECONDS)
    public int benchmarkCountLongWordsUsingStreams() {
        return countLongWordsUsingStreams(words, LONG_WORD_MIN_LEN);
    }
}
public static int countLongWordsWithoutUsingStreams(
    final List<String> words, final int longWordMinLength) {
    final Predicate<String> p = s -> s.length() >= longWordMinLength;

    int count = 0;

    for (String aWord : words) {
        if (p.test(aWord)) {
            ++count;
        }
    }

    return count;
}

public static int countLongWordsUsingStreams(final List<String> words,
    final int longWordMinLength) {
    return (int) words.stream()
    .filter(w -> w.length() >= longWordMinLength).count();
}
Run Code Online (Sandbox Code Playgroud)

Mis*_*sha 5

每当您的基准测试表明超过10000个元素的某些操作需要1ns(编辑:1μs)时,您可能会发现一个聪明的JVM,确定您的代码实际上没有做任何事情.

Collections.nCopies实际上并没有列出10000个元素.它创建了一个带有1个元素的虚假列表,以及它应该存在多少次的计数.该列表也是不可变的,因此countLongWordsWithoutUsingStreams如果有事情removeIf要做,你会抛出异常.

  • @Abhijit Sarkar:首先,您没有使用结果值,因此JVM可能会优化整个计算.其次,它是否依赖于实现,是否实际需要迭代.两种方法都可以使用它们实际上由单个元素组成的知识来仅执行一个谓词测试.但是,由于`nCopies`列表不支持变异,因此不太可能有优化的`removeIf`.但是如果你打算使用一个真正的可变列表,很明显,改变一个`List`比简单地计算匹配更昂贵. (2认同)
  • @Abhijit Sarkar:你似乎错过了我评论的最后一句话:"但是如果你要使用一个真正的可变列表,很明显,改变一个List比仅仅计算匹配更加昂贵.*"这一点尤为真实对于'ArrayList`,其中删除并不便宜.那么这里有什么令人惊讶的?使用`removeIf(...)`来计算出现次数是一个坏主意.为什么不与普通计数循环比较? (2认同)