【问题标题】:Some regression when using initial capacity for ArrayList on the first iterations在第一次迭代中使用 ArrayList 的初始容量时出现一些回归
【发布时间】:2015-05-29 07:25:03
【问题描述】:

我有点困惑。在填充循环的第一次迭代中,我发现使用 initial capacity 处理 ArrayList 与不使用初始容量相比,填充时间出现了一些倒退。

按常识和这个问题:Why start an ArrayList with an initial capacity?

它必须是绝对相反的。

这不是写得很好的基准测试,我想知道:为什么在第一次迭代总是消耗更多的时间和 CPU当使用ArrayList的初始容量时?

这是测试:

public class TestListGen {
    public static final int TEST = 100_000_000;  

    public static void main(String[] args) {
        test(false);
    }    

    private static void test(boolean withInitCapacity) {
        System.out.println("Init with capacity? " + withInitCapacity);
        for (int i = 0; i < 5; i++)
            av += fillAndTest(TEST, withInitCapacity ? new ArrayList<Integer>(TEST) : new ArrayList<Integer>());

        System.out.println("Average: " + (av / 5));
    }    

    private static long fillAndTest(int capacity, List<Integer> list) {
        long time1 = System.nanoTime();
        for (int i = 0; i < capacity; i++) list.add(i);
        long delta = System.nanoTime() - time1;
        System.out.println(delta);
        return delta;
    }
}

输出: 1)

Init with capacity? false
17571882469
12179868327
18460127904
5894883202
13223941250
Average: 13466140630

2)

Init with capacity? true
37271627087
16341545990
19973801769
4888093008
2442179779
Average: 16183449526

我已经测试过:JDK 1.7.0.40, JDK 1.8.0.31

【问题讨论】:

  • 如果您以相反的顺序运行测试(即先有初始容量,然后没有)会发生什么?
  • GC 随机启动。您需要运行它的迭代次数超过 5 次才能进行合理的测量......
  • 对 Java 代码进行基准测试很难。请看stackoverflow.com/questions/504103/…
  • @Andremoniy,我建议您将问题重新表述为“为什么在使用初始容量时我看到 16% 的回归”而不是“嘿,使用初始容量更慢,为什么?”。我认为这是一个有趣的问题(我可以复制,而且我的机器似乎没有你的那么快,但我仍然看到 16% 的差异。)我敢打赌,当有人实际上知道什么是继续前进并为您的问题提供一个体面的答案,我们都会在这里学到一些东西(可能与微基准测试而不是 ArrayList 问题有关)。
  • @Andremoniy。我只是用 100 次迭代而不是 5 次运行它:使用 init false 平均:423499262,使用 init true 平均:177595371,因此初始容量快 2 倍。你不能只使用很少的迭代,特别是如果在这 5 个中你有这样一个范围的值。

标签: java performance arraylist


【解决方案1】:

这是一个 Java 堆分配工件,它会导致您意想不到的结果。调整初始堆分配,通过从混合中删除堆分配时间,您将看到更一致的结果。此外,您需要确保运行基准测试的进程没有被交换。在我的系统上,TEST = 100_000_000 时出现 OOM 错误,我必须将其减少到 10_000_000 以进行测试。我也一个接一个地跑了test(false)test(true)。请注意在启动时分配堆并在下面的结果中添加显式 gc 如何使各个时间更加一致。重新添加热身对于使测试更加一致也很重要,但我没有为此烦恼。

原始测试

Init with capacity? false
1714537208
1259523722
1215986030
1098740959
1029914697
Average: 1263740523
Init with capacity? true
343100302
612709138
355210333
603609642
348401796
Average: 452606242

-Xms500m -Xmx500m测试

Init with capacity? false
682827716
738137558
576581143
662777089
555706338
Average: 643205968
Init with capacity? true
368245589
312674836
297705054
392935762
307209139
Average: 335754076

fillAndTest() 之前使用-Xms500m -Xmx500m + System.gc() 进行测试

Init with capacity? false
502767979
435508363
420956590
487184801
416041923
Average: 452491931
Init with capacity? true
300744404
298446734
299080656
300084036
298473576
Average: 299365881

【讨论】:

  • @Andremoniy 当然可以。您从混合中消除堆分配/gc 所采取的步骤越多,使用和不使用初始化容量的构造之间的性能就越接近,这难道不是很有趣吗?这证明了将阵列容量动态增加 1.5 倍的魔力。
猜你喜欢
  • 2016-10-31
  • 1970-01-01
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多