【问题标题】:Fastest way to read/store lots of multidimensional data? (Java)读取/存储大量多维数据的最快方法? (爪哇)
【发布时间】:2011-02-27 08:38:49
【问题描述】:

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

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 赋值

int value = 1; // test value

仅需 66 毫秒。

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

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

这需要超过 4 秒!

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

【问题讨论】:

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


    【解决方案1】:
    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 秒。

    这似乎很快..您正在查看最内层循环中的 4800 万个元素。

    Homerolling 一个愚蠢的小数据结构..

    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 是一种面向对象的语言。在大多数情况下,你应该使用对象而不是奇怪的数据结构,比如 int[][][]

    【讨论】:

    • 所以只要我不必切换索引,int[][][] 方法更快。
    【解决方案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】:

      你试过了吗:

      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;
              }
          }
      }
      

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

      【讨论】:

      • 现在,我收到了 OutOfMemoryError,我需要弄清楚如何增加堆空间,但我会记住您的建议。
      【解决方案4】:

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

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

      缓存不够大,无法容纳整个多维数组,因此 Java 必须不断地在内存中更新缓存。缓存访问时间比内存访问时间快得多。

      我什至不明白你为什么要做这个测试。如果您需要将大量数据存储在多维数组中,使用单个变量也无济于事,即使它更快。

      此外,在访问数组时切换参数的原因是因为与仅以其他方式迭代时相比,您在内存中跳转的次数要多得多(缓存未命中次数要多得多)。

      【讨论】:

      • +1 表示“我什至不明白你为什么要做这个测试”——这艘潜艇比这个降落伞快得多,那么我应该使用哪一个来安全地从飞机上掉下来?
      • 我不同意。我曾经使用 4D 数组 (int[400][300][400][3]) 来存储 rgb 值。但现在我知道这比使用三个不同的 int[400][300][400] 数组要慢。在像 PHP 这样的语言中,我什至会考虑制作 400x300x400 不同的单个整数并动态调用它们,如果这样更快 - 并且可能在内存方面。
      • 这不是您可以同意或不同意的事情。这不是意见。数组是连续的内存块。这在多维数组中不会改变。从 4 维数组更改为 3、3 维数组不应更改这些数组中数据的访问时间。
      • “在像 PHP 这样的语言中,我什至会考虑制作 400x300x400 不同的单个整数并动态调用它们,如果这样更快的话”什么! (@RemiX)
      【解决方案5】:

      考虑到数组很大,使用的内存量,需要的间接寻址(多维数组是对数组的引用数组......),这对我来说似乎一点也不慢。当您切换 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;
              }
          }
      }
      

      【讨论】:

      • 平面数组与按正确顺序迭代的多维数组一样快。
      • 没错,我说慢,但我只是将它与更快的东西进行比较;)不幸的是,平面数组似乎更慢: values[x*400*300+y*300+d] = 1);花了 300 毫秒
      • @jjnguy,对于 one-flat-array 版本,边界检查可能更少(只有 1 次而不是 3 次)。可能会被 JIT 优化掉,不确定。
      • 如果你进行计算(乘法和求和)会更慢,而不是如果你保持增加的索引,正如我写的那样。但是jinguy是对的,这和标准的多维一样快
      • 做了实验,上面发布的时间 - 在平面数组上进行数学运算似乎比三重嵌套数组快 10%(JIT 很有可能优化了最内部循环)。当然,在实际应用中,访问时间可能会被实际计算相形见绌……
      猜你喜欢
      • 2010-11-11
      • 1970-01-01
      • 2014-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-07
      • 2014-03-03
      • 2014-05-02
      相关资源
      最近更新 更多