简体   繁体   English

Java数组分配比List.add()慢

[英]java Array assignment slowly than List.add()

package test;

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

public class ArrayAndList {
    public static void main(String[] args) {
        int num = 10000000;
        Integer[] mArray = new Integer[num];
        List<Integer> mList = new ArrayList<>(num);

        // array init test
        long iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mArray[i] = i;
        }
        System.out.println("array init:" + (System.currentTimeMillis() - iCurr));

        // list init test
        iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mList.add(i);
        }
        System.out.println("list init:" + (System.currentTimeMillis() - iCurr));

        // array get test
        long mExTimeStamp = System.currentTimeMillis();
        long r1 = 0;
        for (int i = 0; i < num; i++) {
            r1 += mArray[i];
        }
        System.out.println("array get:" + (System.currentTimeMillis() - mExTimeStamp));

        // list get test
        mExTimeStamp = System.currentTimeMillis();
        long r2 = 0;
        for (int i = 0; i < num; i++) {
            r2 += mList.get(i);
        }
        System.out.println("list get:" + (System.currentTimeMillis() - mExTimeStamp));
        if (r2 == r1) {
            System.out.println("correct");
        } else {
            System.out.println("error");
        }
    }
}

Result: 结果:

array init:3312
list init:3029
array get:19
list get:23
correct

after several times test , List init always faster than Array assignment. 经过多次测试,List init总是比Array分配快。 Why ? 为什么呢 why assignment slower than add()? 为什么赋值比add()慢? As I know List composed of Array ? 据我所知List是由Array组成的? why it can be faster than Array on assignment. 为什么它可以比分配数组快。

Improved: 改进的:

package test;

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

public class ArrayAndList {
    public static void main(String[] args) {
        int num = 10000000;
        Integer[] mArray = new Integer[num];
        List<Integer> mList = new ArrayList<>(num);

        // array init test
        long iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mArray[i] = i;
        }
        System.out.println("array init:" + (System.currentTimeMillis() - iCurr));

        // list init test
        iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mList.add(i);
        }
        System.out.println("list init:" + (System.currentTimeMillis() - iCurr));

        mList.clear();
        // second init List
        iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mList.add(i);
        }
        System.out.println("second list init:" + (System.currentTimeMillis() - iCurr));

        // second init Array
        iCurr = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            mArray[i] = i;
        }
        System.out.println("second array init:" + (System.currentTimeMillis() - iCurr));

        // array get test
        long mExTimeStamp = System.currentTimeMillis();
        long r1 = 0;
        for (int i = 0; i < num; i++) {
            r1 += mArray[i];
        }
        System.out.println("array get:" + (System.currentTimeMillis() - mExTimeStamp));

        // list get test
        mExTimeStamp = System.currentTimeMillis();
        long r2 = 0;
        for (int i = 0; i < num; i++) {
            r2 += mList.get(i);
        }
        System.out.println("list get:" + (System.currentTimeMillis() - mExTimeStamp));
        if (r2 == r1) {
            System.out.println("correct");
        } else {
            System.out.println("error");
        }
    }
}

Result: 结果:

array init:3331
list init:2867
second list init:725
second array init:814
array get:20
list get:25
correct

After improved test Method , test show more interesting and confusing Result. 经过改进的测试方法,测试结果更加有趣和令人困惑。 Assignment and add() both be more faster. Assignment和add()都更快。 But assignment is still slower than assignment. 但是分配仍然比分配慢。

Your benchmark is absolutely wrong: your code is not hot enough. 您的基准测试绝对是错误的:您的代码不够热。 Let's repeat creation and adding 10 times for example: 让我们重复创建并添加10次,例如:

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

public class ArrayAndList {
    public static Integer[] createArray(int n) {
        Integer[] arr = new Integer[n];
        for (int i = 0; i < n; i++)
            arr[i] = i;
        return arr;
    }

    public static List<Integer> createList(int n) {
        List<Integer> list = new ArrayList<>(n);
        for (int i = 0; i < n; i++)
            list.add(i);
        return list;
    }

    public static void main(String[] args) {
        int num = 10000000;
        for (int i = 0; i < 10; i++) {
            System.gc();
            {
                long start = System.currentTimeMillis();
                List<Integer> l = createList(num);
                long end = System.currentTimeMillis();
                System.out.println("List: " + l.size() + "; time: "
                        + (end - start));
            }
            System.gc();
            {
                long start = System.currentTimeMillis();
                Integer[] a = createArray(num);
                long end = System.currentTimeMillis();
                System.out.println("Arr: " + a.length + "; time: "
                        + (end - start));
            }
        }
    }
}

My box outputs the following numbers: 我的盒子输出以下数字:

List: 10000000; time: 7838
Arr: 10000000; time: 6615
List: 10000000; time: 1429
Arr: 10000000; time: 1466
List: 10000000; time: 893
Arr: 10000000; time: 5005
List: 10000000; time: 977
Arr: 10000000; time: 468
List: 10000000; time: 530
Arr: 10000000; time: 218
List: 10000000; time: 228
Arr: 10000000; time: 227
List: 10000000; time: 243
Arr: 10000000; time: 224
List: 10000000; time: 236
Arr: 10000000; time: 226
List: 10000000; time: 232
Arr: 10000000; time: 225
List: 10000000; time: 241
Arr: 10000000; time: 249

So after JIT-compilation, profiling and optimization both tests run equally fast and about 30 times faster than the first iteration which is a random mix of interpreted, C1-compiled and C2-compiled code. 因此,在JIT编译之后,性能分析和优化两个测试的运行速度均相同,并且比第一次迭代的速度快约30倍,后者是解释,C1编译和C2编译的代码的随机混合。 Nobody cares about first iteration if your program works long enough. 如果您的程序工作时间足够长,那么没人关心第一次迭代。 Results are equal, because JIT-compiler is smart and it can remove the unnecessary bounds checking. 结果是相等的,因为JIT编译器很聪明,并且可以删除不必要的边界检查。

In future please use JMH to conduct your benchmark tests. 将来,请使用JMH进行基准测试。

Array assignment or List.add takes almost no time in your case. 在您的情况下,数组赋值或List.add几乎不需要时间。 The thing you are really benchmarking is garbage collector. 您真正进行基准测试的是垃圾收集器。 Scanning through 40 million objects is going to take a lot of time. 扫描4000万个对象将花费大量时间。

Garbage collector is unpredictable. 垃圾收集器是不可预测的。 You don't known when full GC will be triggered and when heap size will be increased. 您不知道何时将触发完全GC,何时将增加堆大小。 Those time differences are the effect of that. 那些时差就是这种效果。 If you do more passes, you will see almost no difference between list and array. 如果执行更多遍,列表和数组之间几乎没有区别。

Print Gc Info, and When I init the list first , the result is List init slower than Array init. 打印Gc Info,当我首先初始化列表时,结果是列表初始化比数组初始化慢。 then I print the GC information. 然后我打印GC信息。 The first init will trigger more GC operation, then the first would be slower. 第一个初始化将触发更多的GC操作,然后第一个会更慢。

-XX:+PrintGC

int num = 10000000;
Integer[] mArray = new Integer[num];
List<Integer> mList = new ArrayList<>(num);

Result: 结果:

[GC (Allocation Failure)  111405K->109709K(143360K), 0.1795657 secs]
[Full GC (Ergonomics)  109709K->109633K(229376K), 0.6283730 secs]
[GC (Allocation Failure)  142913K->143015K(242176K), 0.2237062 secs]
[GC (Allocation Failure)  189095K->189183K(262656K), 0.2695423 secs]
[Full GC (Ergonomics)  189183K->188980K(408576K), 1.6973847 secs]
list init:3084
[GC (Allocation Failure)  255540K->255012K(414208K), 0.2481102 secs]
[GC (Allocation Failure)  327204K->327364K(414208K), 0.3935178 secs]
[Full GC (Ergonomics)  327364K->327061K(625664K), 2.0468699 secs]
array init:2734
[GC (Allocation Failure)  399253K->399381K(742912K), 0.3149823 secs]
[GC (Allocation Failure)  505877K->505989K(748032K), 0.6380493 secs]
second list init:1014
[GC (Allocation Failure)  612485K->612637K(820736K), 0.6460302 secs]
[Full GC (Ergonomics)  612637K->391091K(957952K), 3.1096489 secs]
second array init:3795
array get:19
list get:27
correct

change the type of Array, int type would not new Object. 更改Array的类型,则int类型不会新建Object。 Without GC, Array init is very fast. 没有GC,Array初始化非常快。

-XX:+PrintGC

int num = 10000000;
int[] mArray = new int[num];
List<Integer> mList = new ArrayList<>(num);

Result: 结果:

[GC (Allocation Failure)  111405K->109709K(143360K), 0.1745422 secs]
[Full GC (Ergonomics)  109709K->109633K(229376K), 0.6432035 secs]
[GC (Allocation Failure)  142913K->142990K(241664K), 0.2102660 secs]
[GC (Allocation Failure)  188558K->188662K(262656K), 0.2762992 secs]
[Full GC (Ergonomics)  188662K->188469K(408064K), 1.7465284 secs]
list init:3130
array init:12
[GC (Allocation Failure)  255029K->208029K(412672K), 0.1007355 secs]
[GC (Allocation Failure)  279197K->279341K(412672K), 0.3963681 secs]
[Full GC (Ergonomics)  279341K->169276K(461312K), 1.7562075 secs]
second list init:2322
second array init:10
array get:10
list get:26
correct

Change JVM Paramters 更改JVM参数

-XX:+PrintGC -Xms3g -Xmx3g

int num = 10000000;
Integer[] mArray = new Integer[num];
List<Integer> mList = new ArrayList<>(num);

Result: 结果:

list init:131
array init:117
second list init:129
second array init:116
array get:25
list get:24
correct

without the GC operation , List init and Array init is both speed up, And array init is fast than list init. 没有GC操作,List init和Array init都可以加快,而array init比list init快。 All thing is about the GC operation , thank you everyone 一切都与GC操作有关,谢谢大家

You should try to split the tests up: Instead of testing every method in the same script, split it up in one script per method each and test it then to ensure that the different parts don't influence each other (generally always write a test script as compact as possible). 您应该尝试拆分测试:不要在同一个脚本中测试每个方法,而应在每个方法一个脚本中拆分它,然后对其进行测试,以确保不同部分不会相互影响(通常总是编写一个测试)脚本尽可能紧凑)。

I'm not sure if it makes a difference, but it's possible that Java optimizes some things during the mList.clear() 我不确定是否会有所不同,但是Java可能会在mList.clear()期间优化某些功能

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM