编程语言
首页 > 编程语言> > java-具有JMH和Mode.AverageTime的OutOfMemory

java-具有JMH和Mode.AverageTime的OutOfMemory

作者:互联网

我正在编写一个微基准测试,以比较使用运算符vs StringBuilder的字符串连接.为此,我创建了一个基于OpenJDK example that uses the batchSize parameter的JMH基准测试类:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@Measurement(batchSize = 10000, iterations = 10)
@Warmup(batchSize = 10000, iterations = 10)
@Fork(1)
public class StringConcatenationBenchmark {

    private String string;

    private StringBuilder stringBuilder;

    @Setup(Level.Iteration)
    public void setup() {
        string = "";
        stringBuilder = new StringBuilder();
    }

    @Benchmark
    public void stringConcatenation() {
        string += "some more data";
    }

    @Benchmark
    public void stringBuilderConcatenation() {
        stringBuilder.append("some more data");
    }

}

当运行基准测试时,stringBuilderConcatenation方法出现以下错误:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:421)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at link.pellegrino.string_concatenation.StringConcatenationBenchmark.stringBuilderConcatenation(StringConcatenationBenchmark.java:29)
    at link.pellegrino.string_concatenation.generated.StringConcatenationBenchmark_stringBuilderConcatenation.stringBuilderConcatenation_avgt_jmhStub(StringConcatenationBenchmark_stringBuilderConcatenation.java:165)
    at link.pellegrino.string_concatenation.generated.StringConcatenationBenchmark_stringBuilderConcatenation.stringBuilderConcatenation_AverageTime(StringConcatenationBenchmark_stringBuilderConcatenation.java:130)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

我当时在想必须增加默认的JVM堆大小,因此我尝试使用-Xmx10G值和JMH提供的-jvmArgs选项,最多允许10GB.不幸的是,我仍然得到错误.

因此,我试图将batchSize参数的值减小为1,但仍然出现OutOfMemoryError.

我发现的唯一解决方法是将基准模式设置为Mode.SingleShotTime.由于此模式似乎将批处理视为单发(即使s / op显示在“单位”列中),因此似乎可以得到所需的指标:执行批处理操作集的平均时间.但是,我仍然不明白为什么它不能与Mode.AverageTime一起使用.

还请注意,无论使用哪种基准方式,方法stringConcatenation的基准均按预期工作.仅使用StringBuilder的stringBuilderConcatenation方法会出现此问题.

欢迎您理解任何帮助,以了解为什么前面的示例无法将Benchmark模式设置为Mode.AverageTime.

我使用的JMH版本是1.10.4.

解决方法:

您说对了,Mode.SingleShotTime是您所需要的:它可以测量单个批次的时间.使用Mode.AverageTime时,您的迭代仍将继续进行直到迭代时间结束(默认为1秒).它测量每次执行单个批次的时间(仅计算在执行时间内完全完成的批次),因此最终结果有所不同,但是执行时间相同.

另一个问题是@Setup(Level.Iteration)强制设置在每次迭代之前执行,而不是在每个批处理之前执行.因此,您的字符串实际上不受批量大小的限制.字符串版本不会导致OutOfMemoryError仅仅是因为它比StringBuilder慢得多,因此在1秒钟内它能够构建更短的字符串.

修复基准的一种不太好的方法(虽然仍使用平均时间模式和batchSize参数)是手动重置string / stringBuilder:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(batchSize = 10000, iterations = 10)
@Warmup(batchSize = 10000, iterations = 10)
@Fork(1)
public class StringConcatenationBenchmark {
    private static final String S = "some more data";
    private static final int maxLen = S.length()*10000;

    private String string;

    private StringBuilder stringBuilder;

    @Setup(Level.Iteration)
    public void setup() {
        string = "";
        stringBuilder = new StringBuilder();
    }

    @Benchmark
    public void stringConcatenation() {
        if(string.length() >= maxLen) string = "";
        string += S;
    }

    @Benchmark
    public void stringBuilderConcatenation() {
        if(stringBuilder.length() >= maxLen) stringBuilder = new StringBuilder();
        stringBuilder.append(S);
    }
}

这是我的机器上的结果(i5 3340、4Gb RAM,64位Win7,JDK 1.8.0_45):

Benchmark                   Mode  Cnt       Score       Error  Units
stringBuilderConcatenation  avgt   10     145.997 ±     2.301  us/op
stringConcatenation         avgt   10  324878.341 ± 39824.738  us/op

因此,您可以看到对于stringConcatenation(1e6 / 324878),只有大约3个批处理适合第二个批处理,而对于stringBuilderConcatenation,可以执行数千个批处理,从而导致巨大的字符串,从而导致OutOfMemoryError.

我不知道为什么增加内存对您不起作用,对我来说-Xmx4G足以运行原始基准测试的stringBuilder测试.可能是您的盒子更快,所以生成的字符串甚至更长.请注意,对于非常大的字符串,即使您有足够的内存,也可以达到数组大小限制(20亿个元素).添加内存后检查异常stacktrace:是否相同?如果达到数组大小限制,则仍然是OutOfMemoryError,但是stacktrace会有所不同.无论如何,即使有足够的内存,您的基准测试结果也不正确(对于String和StringBuilder而言).

标签:jmh,stringbuilder,java
来源: https://codeday.me/bug/20191119/2039322.html