读取/存储大量多维数据的最快方法? (JAVA)

时间:2010-06-03 14:42:13

标签: java performance data-structures arrays multidimensional-array

我有三个关于三个嵌套循环的问题:

for (int x=0; x<400; x++)
{
    for (int y=0; y<300; y++)
    {
        for (int z=0; z<400; z++)
        {
             // compute and store value
        }
    }
}

我需要存储所有计算值。我的标准方法是使用3D阵列:

values[x][y][z] = 1; // test value

但事实证明这很慢:完成这个循环需要192毫秒,其中一个int-assignment

int value = 1; // test value

只需66毫秒。

1)为什么数组如此相对较慢?
2)当我把它放在内循环中时,为什么它变得更慢:

values[z][y][x] = 1; // (notice x and z switched)

这需要超过4秒!

3)最重要的是:我可以使用与分配单个整数一样快的数据结构,但可以存储与3D数组一样多的数据吗?

5 个答案:

答案 0 :(得分:3)

public static void main( String[] args ) {

    int[][][] storage = new int[ 400 ][ 300 ][ 400 ];
    long start = System.currentTimeMillis();

    for ( int x = 0; x < 400; x++ ) {
        for ( int y = 0; y < 300; y++ ) {
            for ( int z = 0; z < 400; z++ ) {
                storage[x][y][z] = 5;
            }
        }
    }

    long end = System.currentTimeMillis();
    System.out.println( "Time was: " + ( end - start ) / 1000.0 + " seconds." );


}

使用-Xmx1g

时间是:0.188秒。

这看起来非常快......你在最里面的循环中看到了48百万个元素。

正在滚动一个愚蠢的小数据结构..

public static void main( String[] args ) {

    StorerGuy[] storerGuys = new StorerGuy[ 400 ];

    long start = System.currentTimeMillis();

    for ( int x = 0; x < 400; x++ ) {
        for ( int y = 0; y < 300; y++ ) {
            for ( int z = 0; z < 400; z++ ) {
                storerGuys[x] = new StorerGuy( x, y, z, 5 );

            }
        }
    }

    long end = System.currentTimeMillis();
    System.out.println( "Time was: " + ( end - start ) / 1000.0 + " seconds." );

}

public static class StorerGuy {

    public int x;
    public int y;
    public int z;
    public int value;

    StorerGuy( int x, int y, int z, int value ) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.value = value;
    }

}

时间是:0.925秒。

比混合订单示例中的时间快4秒。

我认为多个阵列对于这个问题来说太过分了。使用更复杂的数据结构会更好,因为它会将所有内容保存在1个内存位置(x,y,z,值)。

Java是一种OO语言。在大多数情况下,您应该使用对象而不是像[[] [] []

这样奇怪的数据结构

答案 1 :(得分:2)

你试过这个:

Object[][][] store = new Object[ 400 ][300][400];

for (int x=0; x<400; x++)
{
    Object[][] matrix = store[x];

    for (int y=0; y<300; y++)
    {
        Object[] line = matrix[y];
        for (int z=0; z<400; z++)
        {
             // compute and store value
             line[z] = // result;
        }
    }
}

它可能会改善您的缓存抖动。

答案 2 :(得分:2)

  

1)为什么数组如此相对较慢?

正如其他人指出的那样,你正在将苹果与橙子进行比较。三重数组很慢,因为它需要三次取消引用(内部至少 - 是的,“Java中没有指针”);但话又说回来,你不能引用一个整数变量...

  

2)当我把它放在内循环中时,为什么它变得更慢:

values[z][y][x] = 1; // (notice x and z switched)

因为您降低了缓存一致性。变化最快的索引应该是最后的索引,因此大多数内存访问在相同的缓存块中彼此相邻,而不是强迫处理器等到从主RAM读取块之后。

  

3)最重要的是:我可以使用与分配单个整数一样快的数据结构,但可以存储与3D数组一样多的数据吗?

没有。没有这样的结构,因为整数变量适合机器寄存器(甚至比处理器的内存缓存更快),并且总是可以比你想要提到的任何其他东西更快地访问。处理器速度比主存速度快得多。如果你的'工作集'(你需要操作的数据)不适合寄存器或缓存,你将不得不支付罚款从RAM(或更糟糕的是,磁盘)获取它。

这就是说,Java对每个阵列访问进行边界检查,并且似乎不太聪明地优化边界检查。以下比较可能是有意义的:

public static long test1(int[][][] array) {
    long start = System.currentTimeMillis();
    for ( int x = 0; x < 400; x++ ) {
        for ( int y = 0; y < 300; y++ ) {
            for ( int z = 0; z < 400; z++ ) {
                array[x][y][z] = x + y + z;
            }
        }
    }
    return System.currentTimeMillis() - start;
}

public static long test2(int [] array) {
    long start = System.currentTimeMillis();
    for ( int x = 0; x < 400; x++ ) {
        for ( int y = 0; y < 300; y++ ) {
            for ( int z = 0; z < 400; z++ ) {
                array[z + y*400 + x*400*300] = x + y + z;
            }
        }
    }
    return System.currentTimeMillis() - start;
}

public static void main(String[] args) {

    int[][][] a1 = new int[400][300][400];
    int[] a2 = new int[400*300*400];
    int n = 20;

    System.err.println("test1");
    for (int i=0; i<n; i++) {
        System.err.print(test1(a1) + "ms ");
    }
    System.err.println();
    System.err.println("test2");
    for (int i=0; i<n; i++) {
        System.err.print(test2(a2) + "ms ");
    }
    System.err.println();
}

我的系统上的输出是

test1
164ms 177ms 148ms 149ms 148ms 147ms 150ms 151ms 152ms 154ms 151ms 150ms 148ms 148ms 150ms 148ms 150ms 148ms 148ms 149ms 
test2
141ms 153ms 130ms 130ms 130ms 133ms 130ms 130ms 130ms 132ms 129ms 131ms 130ms 131ms 131ms 130ms 131ms 130ms 130ms 130ms

因此,还有一些改进空间......但我认为这不值得你这么做。

答案 3 :(得分:1)

我猜这与缓存和寄存器以及内存局部性原理有很大关系。

当存储到数组中时,Java必须访问数千个字节的内存。使用单个变量,它可以将该值保留在缓存中,并且只是不断更新它。

缓存不足以容纳整个多维数组,因此Java必须不断更新内存中的缓存。缓存访问时间比内存访问时间快。

我甚至不明白为什么你会做这个测试。如果需要在多维数组中存储大量数据,使用单个变量没有帮助,即使它更快。

另外,在访问数组时切换参数的原因是因为你在内存中跳转的次数要多得多(缓存未命中次数多),而不是只是以另一种方式进行迭代。

答案 4 :(得分:0)

考虑到数组是巨大的,使用的内存量,所需的间接(多维数组是对数组的引用数组......),这对我来说似乎并不慢。当你切换x和z时,你可能正在破坏缓存。

为了比较,您可以将所有内容存储在一个平面数组中......这样可以提高存储速度......但是检索会更复杂,速度也更慢。

int k = 0;
for (int x=0; x<400; x++)
{
    for (int y=0; y<300; y++)
    {
        for (int z=0; z<400; z++)
        {
             // compute and store value
             arr[k++] = val;
        }
    }
}