验证简单的for/lambda比较操作的JMH测量结果

4

我想要对简单for循环和等效的流实现进行性能测量和比较。我相信流会比等效的非流代码稍微慢一些,但我想确保我正在测量正确的东西。

我在这里包含了整个jmh类。

import java.util.ArrayList;
import java.util.List;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;

@State(Scope.Benchmark)
public class MyBenchmark {
    List<String>    shortLengthListConstantSize     = null;
    List<String>    mediumLengthListConstantSize    = null;
    List<String>    longerLengthListConstantSize    = null;
    List<String>    longLengthListConstantSize      = null;

    @Setup
    public void setup() {
        shortLengthListConstantSize     = populateList(2);
        mediumLengthListConstantSize    = populateList(12);
        longerLengthListConstantSize    = populateList(300);
        longLengthListConstantSize      = populateList(300000);
    }

    private List<String> populateList(int size) {
        List<String> list   = new ArrayList<>();
        for (int ctr = 0; ctr < size; ++ ctr) {
            list.add("xxx");
        }
        return list;
    }

    @Benchmark
    public long shortLengthConstantSizeFor() {
        long count   = 0;
        for (String val : shortLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long shortLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        shortLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long shortLengthConstantSizeLambda() {
        return shortLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long shortLengthConstantSizeLambdaParallel() {
        return shortLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long mediumLengthConstantSizeFor() {
        long count   = 0;
        for (String val : mediumLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long mediumLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        mediumLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long mediumLengthConstantSizeLambda() {
        return mediumLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long mediumLengthConstantSizeLambdaParallel() {
        return mediumLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longerLengthConstantSizeFor() {
        long count   = 0;
        for (String val : longerLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long longerLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        longerLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long longerLengthConstantSizeLambda() {
        return longerLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longerLengthConstantSizeLambdaParallel() {
        return longerLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longLengthConstantSizeFor() {
        long count   = 0;
        for (String val : longLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long longLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        longLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long longLengthConstantSizeLambda() {
        return longLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longLengthConstantSizeLambdaParallel() {
        return longLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    public static class IntHolder {
        public int value    = 0;
    }
}

我正在一台Windows 7笔记本电脑上运行这些程序。我不关心绝对的测量结果,只关心相对的。以下是它们的最新结果:

Benchmark                                            Mode  Cnt          Score         Error  Units
MyBenchmark.longLengthConstantSizeFor               thrpt  200       2984.554 ±      57.557  ops/s
MyBenchmark.longLengthConstantSizeForEach           thrpt  200       2971.701 ±     110.414  ops/s
MyBenchmark.longLengthConstantSizeLambda            thrpt  200        331.741 ±       2.196  ops/s
MyBenchmark.longLengthConstantSizeLambdaParallel    thrpt  200       2827.695 ±     682.662  ops/s
MyBenchmark.longerLengthConstantSizeFor             thrpt  200    3551842.518 ±   42612.744  ops/s
MyBenchmark.longerLengthConstantSizeForEach         thrpt  200    3616285.629 ±   16335.379  ops/s
MyBenchmark.longerLengthConstantSizeLambda          thrpt  200    2791292.093 ±   12207.302  ops/s
MyBenchmark.longerLengthConstantSizeLambdaParallel  thrpt  200      50278.869 ±    1977.648  ops/s
MyBenchmark.mediumLengthConstantSizeFor             thrpt  200   55447999.297 ±  277442.812  ops/s
MyBenchmark.mediumLengthConstantSizeForEach         thrpt  200   57381287.954 ±  362751.975  ops/s
MyBenchmark.mediumLengthConstantSizeLambda          thrpt  200   15925281.039 ±   65707.093  ops/s
MyBenchmark.mediumLengthConstantSizeLambdaParallel  thrpt  200      60082.495 ±     581.405  ops/s
MyBenchmark.shortLengthConstantSizeFor              thrpt  200  132278188.475 ± 1132184.820  ops/s
MyBenchmark.shortLengthConstantSizeForEach          thrpt  200  124158664.044 ± 1112991.883  ops/s
MyBenchmark.shortLengthConstantSizeLambda           thrpt  200   18750818.019 ±  171239.562  ops/s
MyBenchmark.shortLengthConstantSizeLambdaParallel   thrpt  200     474054.951 ±    1344.705  ops/s

在早期的问题中,我确认这些基准测试似乎是“功能等效的”(只是寻求额外的眼睛)。这些数字是否与这些基准测试的独立运行相一致?
另一件我一直不确定的事情是JMH输出中究竟代表什么,特别是吞吐量数字。例如,“Cnt”列中的“200”到底代表什么?吞吐量单位是“每秒操作次数”,那么“操作”究竟代表什么,是执行一次基准测试方法的调用吗?例如,在最后一行中,这将代表一秒钟内对基准测试方法进行474k次执行。
更新:
我注意到当我将“for”与“lambda”进行比较时,从“short”列表开始到更长的列表,它们之间的比率非常大,但会逐渐降低,直到“long”列表,其中比率甚至比“short”列表还要大(14%,29%,78%和11%)。我觉得这很奇怪。我本来期望随着实际业务逻辑的工作量增加,流开销的比率会降低。您有任何想法吗?

提示:JMH支持不需要为不同的输入重复代码的设置。 - Holger
明白了,我会将其纳入下一个版本中。 - David M. Karr
1个回答

1
例如,“Cnt”列中的“200”究竟代表什么?
“cnt”列是迭代次数-即测试重复执行的次数。您可以使用以下注释控制该值:
对于实际测量:@Measurement(iterations = 10, time = 50, timeUnit = TimeUnit.MILLISECONDS)
对于预热阶段:@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
这里,“iterations”就是“cnt”;“time”是一个迭代所需的时间,而“timeUnit”是“time”的度量单位。
吞吐量单位为“每秒操作次数”。
您可以通过多种方式控制输出。例如,您可以使用@OutputTimeUnit(TimeUnit.XXXX)更改时间的度量单位,以便获得ops/us、ops/ms等。
您还可以更改mode:而不是测量ops / time,您可以测量“平均时间”,“样本时间”等。您可以通过@BenchmarkMode({Mode.AverageTime})注释来控制这一点。

那么“操作”到底代表什么,是对基准测试方法调用的一次执行吗?

假设一个迭代需要1秒钟,您获得1000个ops / sec。这意味着基准测试方法已经被执行了1000次。
换句话说,一个操作是基准测试方法的一次执行,除非您具有@OperationsPerInvocation(XXX)注释,这意味着每个方法的调用将计为XXX个操作。
误差在所有迭代中计算。

还有一个提示:不要硬编码每个可能的大小,可以进行参数化基准测试:

@Param({"3", "12", "300", "3000"})
private int length;

然后您可以在设置中使用该参数:
 @Setup(Level.Iteration)
 public void setUp(){
     populateList(length)
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接