如何正确使用JMH?ArrayList 示例

Kir*_* Ch 6 java performance jmh

在我的示例中,理论上 2 种方法的性能应该非常相似。在第一种情况下,我使用数组,在第二种情况下 - ArrayList 具有保证的容量。

结果如下:

LessonBenchmark2.capacityTestArray avgt 5 1,354 ± 0,057 ms/op

LessonBenchmark2.capacityTestArrayListEnsured avgt 5 32,018 ± 81,911 ms/op

这里似乎数组要快得多(1.354 vs 32.018 ms/op)。可能是我的 JMH 基准测试设置不正确。如何做对?

此外,如果我使用 @Setup(Level.Invocation),那么结果很接近(1,405 对 1,496 ms/op):

LessonBenchmark.capacityTestArray avgt 5 1,405 ± 0,143 ms/op

LessonBenchmark.capacityTestArrayListEnsured avgt 5 1,496 ± 0,104 ms/op

但是据说要小心使用 Invocation 。此外,迭代模式在逻辑上似乎是正确的。

这是代码:

public static void main(String[] args) throws Exception {
    org.openjdk.jmh.Main.main(args);
}

static final int iter = 5;
static final int fork = 1;
static final int warmIter = 5;

@State(Scope.Benchmark)
public static class Params {
    public int length = 100_000;
    public Person[] people;
    public ArrayList<Person> peopleArrayListEnsure;

    // before each iteration of the benchmark
    @Setup(Level.Iteration)
    public void setup() {
        people = new Person[length];
        peopleArrayListEnsure = new ArrayList<>(length);
    }
}

@Benchmark
@Warmup(iterations = warmIter)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = fork)
@Measurement(iterations = iter)
public void capacityTestArray(Params p) {
    for (int i = 0; i < p.length; i++) {
        p.people[i] = new Person(i, new Address(i, i), new Pet(i, i));
    }
}

@Benchmark
@Warmup(iterations = warmIter)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = fork)
@Measurement(iterations = iter)
public void capacityTestArrayListEnsured(Params p) {
    for (int i = 0; i < p.length; i++) {
        p.peopleArrayListEnsure.add(new Person(i, new Address(i, i), new Pet(i, i)));
    }
}

public static class Person {
    private int id;
    private Address address;
    private Pet pet;

    public Person(int id, Address address, Pet pet) {
        this.id = id;
        this.address = address;
        this.pet = pet;
    }
}

public static class Address {
    private int countryId;
    private int cityId;

    public Address(int countryId, int cityId) {
        this.countryId = countryId;
        this.cityId = cityId;
    }
}

public static class Pet {
    private int age;
    private int typeId;

    public Pet(int age, int typeId) {
        this.age = age;
        this.typeId = typeId;
    }
}
Run Code Online (Sandbox Code Playgroud)

rzw*_*oot 5

测试设计得很糟糕;在您的测试中,由于 arraylist 仅针对多次调用创建一次,因此基于数组的代码只会多次覆盖同一个数组,而 arraylist 版本会添加越来越多的内容,并且需要增长。

\n

一个简单的修复方法是首先清除它。另一个解决方法是停止在此处使用状态,而将对象的创建(无论是 100k 人数组,还是为 100k 人预设大小的人数组列表)作为测试工具的一部分。一旦处理好这一点,考虑到错误,结果是完全相同的,对于 this ,数组和数组列表之间没有任何性能差异。

\n
MyBenchmark.capacityTestArray             avgt    5  1,325 \xc2\xb1 0,059  ms/op\nMyBenchmark.capacityTestArrayListEnsured  avgt    5  1,287 \xc2\xb1 0,157  ms/op\n
Run Code Online (Sandbox Code Playgroud)\n

我通过Params完全删除状态并将列表和数组的创建作为每个测试支出的一部分来进行简化:

\n
    static final int LEN = 100_000;\n    \n    public void capacityTestArray() {\n        Person[] people = new Person[LEN];\n        for (int i = 0; i < LEN; i++) {\n            people[i] = new Person(i, new Address(i, i), new Pet(i, i));\n        }\n    }\n\n    public void capacityTestArrayListEnsured() {\n        List<Person> p = new ArrayList<Person>(LEN);\n        for (int i = 0; i < LEN; i++) {\n            p.add(new Person(i, new Address(i, i), new Pet(i, i)));\n        }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

(保持所有注释和PersonAddress等类相同)。

\n

list.clear()或者,使用现有代码并在顶部添加 a 。

\n


Eug*_*ene 3

一旦您理解了TrialIteration和之间的区别Invocation,您的问题就变得很容易回答。除了样本本身之外,还有什么地方可以更好地理解这些呢?

Invocation是该方法的单次执行。假设有 3 个线程,每个线程执行此基准测试方法 100 次。这意味着Invocation == 300。这就是为什么使用此设置可以获得非常相似的结果。

Iteration来自3上面的例子。

Trial1当所有线程执行它们的所有方法时,将会是这样。

Invocation,虽然有一个可怕的文档有它的用法,就像排序的数据结构;但我也曾在其他地方使用过。的概念也operation可以用@OperationsPerInvocation- 这是另一个锋利的工具来“改变”。


有了这个——就很容易回答了。当你使用 时Iteration,你的意志ArrayList会不断增长——这在内部意味着System::arrayCopy,而你的数组则不然。

一旦你弄清楚了这一点,你需要阅读示例并发现你的第二个问题是你的@Benchmark方法返回void. 而且,与另一个答案相反 - 我不建议用测试方法本身来批量处理所有内容,但这提出了一个问题:您首先要测试什么。不要忘记这些只是数字,最后,您需要推理它们的含义以及如何正确设置测试JMH