使用copyOfRange拆分数组时的奇怪运行时间

时间:2016-06-18 16:08:27

标签: java arrays split

我有一个逗号分隔的文件,有10 977 120个随机数(60 MB),其中我得到了总和。任务是同时做到这一点,因此可能以禁食的方式。长话短说,我将文本文件加载到字符串数组中。我的下一个想法是将这个数组分成四个较小的部分,然后对于每个部分,都有一个对该部分求和的线程。

奇怪的是,当我将列表分成4个部分时,我的运行时间会有很大差异。

我有一个看起来像这样的方法:

public void splitNumbers(String[] numbers){
    int size = numbers.length;
    String[][] numberssplit = new String[4][];
    numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
    numberssplit[1] = Arrays.copyOfRange(numbers, (size/4)+1, size/2);
    numberssplit[2] = Arrays.copyOfRange(numbers, (size/2)+1, 3*(size/4));
    numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4))+1, size-1);
    //MS: 2750
}

以上需要大约2750毫秒

numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
numberssplit[1] = Arrays.copyOfRange(numbers, (size/4)+1, size/2);
//numberssplit[2] = Arrays.copyOfRange(numbers, (size/2)+1, 3*(size/4));
//numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4))+1, size-1);
MS: 5
然而,只有两个部分分开,需要5个MS,看起来它是最后两个需要更长时间的部分。

仅拆分第三部分需要2毫秒

//numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
//numberssplit[1] = Arrays.copyOfRange(numbers, (size/4)+1, size/2);
numberssplit[2] = Arrays.copyOfRange(numbers, (size/2)+1, 3*(size/4));
//numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4))+1, size-1);
//MS: 2

第四部分也是如此,因为它是唯一被拆分的部分。

//numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
//numberssplit[1] = Arrays.copyOfRange(numbers, (size/4)+1, size/2);
//numberssplit[2] = Arrays.copyOfRange(numbers, (size/2)+1, 3*(size/4));
numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4))+1, size-1);
//MS: 2

最后,最后两个未注释,需要2927毫秒

//numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
//numberssplit[1] = Arrays.copyOfRange(numbers, (size/4)+1, size/2);
numberssplit[2] = Arrays.copyOfRange(numbers, (size/2)+1, 3*(size/4));
numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4))+1, size-1);
//MS: 2927

似乎当numbersplit[2]numbersplit[3]合并时需要更长时间,但为什么会这样?显然java,在幕后做了一些魔术,但我无法看到逻辑。那么发生了什么?

2 个答案:

答案 0 :(得分:1)

简短回答:使用-verbose:gc运行项目,输出将为您提供完整的答案。

答案很长:

我使用代码的第一个块进行了测试。 首先,你将在这些范围内松开一个数字:

(size/4)+1, size/2
(size/2)+1, 3*(size/4)

这里有两个数字:

(3*(size/4))+1, size-1

解释here

  

from - 要复制的范围的初始索引(包括)   to - 要复制的范围的最终索引,独占

由于Array.copyOfRange的复杂性为(O(n)),因此这四个部分之间不应有任何时间差异。 但是如果你看到这样的东西意味着由内存分配引起的内存问题。

看看这个:

package testproject;

import java.util.Arrays;

public class TestProject {

    public static void main(String[] args) {
        String[] numbers = getNumbers(0, 10000000);

        long timestamp = System.currentTimeMillis();
        System.out.println("Starting split");
        String[][] splitted = splitNumbers(numbers);
        System.out.println(System.currentTimeMillis() - timestamp);

        timestamp = System.currentTimeMillis();
        System.out.println("Starting split");
        splitted = splitNumbers(numbers);
        System.out.println(System.currentTimeMillis() - timestamp);
    }

    public static String[] getNumbers(int from, int to){
        String[] res = new String[to-from];
        for(int i=0; i<(to-from); i++){
            res[i] = Integer.toString(from + i);
        }
        return res;
    }

    public static String[][] splitNumbers(String[] numbers){
        int size = numbers.length;
        String[][] numberssplit = new String[4][];
        numberssplit[0] = Arrays.copyOfRange(numbers, 0, size/4);
        numberssplit[1] = Arrays.copyOfRange(numbers, (size/4), size/2);
        numberssplit[2] = Arrays.copyOfRange(numbers, (size/2), 3*(size/4));
        numberssplit[3] = Arrays.copyOfRange(numbers, (3*(size/4)), size);
        return numberssplit;
    }
}

这里我们两次调用splitNumbers。

我的电脑上的结果:

    Starting split
    15
    Starting split
    [GC (Allocation Failure)  626829K->625997K(764928K), 0.2908820 secs]
    [Full GC (Ergonomics)  625997K->625210K(1039872K), 3.2126996 secs]
    3510

所以是的,正如你在这里看到的,我们隐藏了对垃圾收集器的调用。 它们会引起你的问题。

答案 1 :(得分:0)

考虑不需要复制数据的方法。

例如,为每个任务提供原始数组,但是可以使用一个范围。

另一种方法是表示为List并使用subList方法,该方法返回视图,而不是副本。