【问题标题】:Efficient implementation of multi-dimensional arrays in Java?Java中多维数组的高效实现?
【发布时间】:2011-06-24 22:50:08
【问题描述】:

据我了解(来自this 等答案),java 没有原生多维连续内存数组(unlike C#, for example)。

虽然锯齿状数组语法(数组的数组)可能适用于大多数应用程序,但如果您确实想要连续内存数组的原始效率(避免不必要的内存读取),我仍然想知道最佳实践是什么

我当然可以使用映射到二维数组的一维数组,但我更喜欢结构化的东西。

【问题讨论】:

  • 我感觉你在微优化。将一维数组映射到二维数组只会删除 1 个数组查找。我无法想象它会为您节省很多/任何时间。
  • 如果您需要所有性能,您可能更适合使用 C 或 C++。
  • 你认为在什么情况下让二维数组的每一行与内存中的下一行相邻会比 java 的普通数组数组显着更有效?跨度>
  • 我并不是说这是一个关键优化,但最好知道最佳实践是什么。

标签: java arrays multidimensional-array


【解决方案1】:

手动操作并不难:

int[] matrix = new int[ROWS * COLS];

int x_i_j = matrix[ i*COLS + j ];

现在,真的比java的多维数组快吗?

int x_i_j = matrix[i][j];

对于随机访问,也许。对于连续访问,可能不是 - matrix[i] 几乎肯定在 L1 缓存中,如果不在寄存器缓存中。在最佳情况下,matrix[i][j] 需要一次加法和一次内存读取;而matrix[i*COLS + j] 可能需要 2 次加法,一次乘法,一次内存读取。但谁在数呢?

【讨论】:

  • 但是,对于单个数组,您只能进行一次边界检查,而不是两次。
  • 如果优化缓存了矩阵[i],那么它的边界检查就少了。
【解决方案2】:

这取决于您的访问模式。使用this simple program,将int[][] 与映射到被视为矩阵的一维int[] 数组上的二维进行比较,原生Java 二维矩阵是:

  1. 当行在缓存中时快 25%,即:按行访问:
  2. 当行不在缓存中时慢100%,即:按列访问:

即:

// Case #1
for (y = 0; y < h; y++)
    for (x = 0; x < w; x++)
        // Access item[y][x]

// Case #2
for (x = 0; x < w; x++)
    for (y = 0; y < h; y++)
        // Access item[y][x]

一维矩阵计算如下:

public int get(int x, int y) {
    return this.m[y * width + x];
}

【讨论】:

  • 不错。所以 vm 优化并没有我想象的那么聪明。
  • 相反,JIT 优化方法调用不仅仅是内联它们。调用 get(x, y) 方法比直接从外部访问矩阵要快。
【解决方案3】:

假设你有一个二维数组int[][] a = new int[height][width],所以按照惯例你有索引a[y][x]。根据您表示数据的方式以及访问它们的方式,性能会以 20 倍的比例变化:

代码:

public class ObjectArrayPerformance {
    public int width;
    public int height;
    public int m[];

    public ObjectArrayPerformance(int w, int h) {
            this.width = w;
            this.height = h;
            this.m = new int[w * h];
    }

    public int get(int x, int y) {
            return this.m[y * width + x];
    }

    public void set(int x, int y, int value) {
            this.m[y * width + x] = value;
    }

    public static void main (String[] args) {
            int w = 1000, h = 2000, passes = 400;

            int matrix[][] = new int[h][];

            for (int i = 0; i < h; ++i) {
                    matrix[i] = new int[w];
            }

            long start;
            long duration;

            System.out.println("duration[ms]\tmethod");

            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                                    matrix[y][x] = matrix[y][x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\t2D array, loop on x then y");

            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    matrix[y][x] = matrix[y][x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\t2D array, loop on y then x");

            //

            ObjectArrayPerformance mt = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    mt.set(x, y, mt.get(x, y) + 1);
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access trough getter/setter");

            //

            ObjectArrayPerformance mt2 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    mt2.m[y * w + x] = mt2.m[y * w + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop y then x");

            ObjectArrayPerformance mt3 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                                    mt3.m[y * w + x] = mt3.m[y * w + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y");

            ObjectArrayPerformance mt4 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        int yIndex = y * w;
                        for (int x = 0; x < w; x++) {
                                    mt4.m[yIndex + x] = mt4.m[yIndex + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y, yIndex optimized");
    }
}

我们可以得出结论,线性访问性能更多地取决于您处理数组的方式(行然后列还是相反?:性能增益 = x10,很大程度上归功于 CPU 缓存)而不是数组本身的结构(1D 与 2D : 性能增益 = x2)。

如果是随机访问,性能差异应该会小很多,因为 CPU 缓存的影响较小。

【讨论】:

    【解决方案4】:

    如果你真的想要更多具有连续内存数组的结构,请将其包装在一个对象中。

    public class My2dArray<T> {
    
      int sizeX;
    
      private T[] items;
    
      public My2dArray(int x, int y) {
        sizeX = x;
        items = new T[x*y];
      }
    
      public T elementAt(int x, int y) {
        return items[x+y*sizeX];
      }
    
    }
    

    不是一个完美的解决方案,您可能已经知道了。因此,请考虑对您怀疑的内容进行确认。

    Java 只为组织代码提供了某些结构,因此最终您必须使用类或接口。由于这也需要特定的操作,因此您需要一个类。

    性能影响包括为每个数组访问创建一个 JVM 堆栈帧,最好避免这样的事情;但是,JVM 堆栈帧是 JVM 实现其作用域的方式。代码组织需要适当的范围界定,所以没有办法解决我可以想象的性能损失(不违反“一切都是对象”的精神)。

    【讨论】:

    • 您还可以概括并删除“2D”并将维度作为 var-arg 参数传递给构造函数(并让 elementAt 为索引采用 var-arg)
    • 是的,但该示例演示了关键思想。可能性很好,他有一个特定的维度数组。
    【解决方案5】:

    示例实现,没有编译器。当您访问多维数组时,这基本上是 C/C++ 在幕后所做的。当指定的尺寸小于实际尺寸等时,您必须进一步定义访问器行为。开销将是最小的,可以进一步优化,但那是微优化恕我直言。此外,在 JIT 启动后,您永远不会真正知道引擎盖下发生了什么。

    class MultiDimentionalArray<T> {
    //disclaimer: written within SO editor, might contain errors
        private T[] data;
        private int[] dimensions; //holds each dimensions' size
    
        public MultiDimensionalArray(int... dims) {
            dimensions = Arrays.copyOf(dims, dims.length);
            int size = 1;
            for(int dim : dims)
                size *= dim;
            data = new T[size];
        }
    
       public T access(int... dims) {
           int idx = 1;
           for(int i = 0; i < dims.length)
                idx += dims[i] * dimensions[i]; //size * offset
           return data[idx];
        }
    }
    

    【讨论】:

      【解决方案6】:

      实现多维数组最有效的方法是将一维数组用作多维数组。请参阅this answer 关于将二维数组映射到一维数组的信息。

      // 2D data structure as 1D array
      int[] array = new int[width * height];
      // access the array 
      array[x + y * width] = /*value*/;
      

      我当然可以使用映射到二维数组的一维数组,但我更喜欢结构化的东西。

      如果您想以更结构化的方式访问array,请为其创建一个类:

      public class ArrayInt {
      
          private final int[] array;
          private final int width, height;
      
          public ArrayInt(int width, int height) {
              array = new int[width * height];
              this.width = width;
              this.height = height;
          }
      
          public int getWidth() {
              return width;
          }
      
          public int getHeight() {
              return height;
          }
      
          public int get(int x, int y) {
              return array[x + y * width];
          }
      
          public void set(int x, int y, int value) {
              array[x + y * width] = value;
          }
      
      }
      

      如果您想要对象数组,可以使用泛型并定义类 Array&lt;T&gt;,其中 T 是存储在数组中的对象。

      在性能方面,在大多数情况下,这将比 Java 中的多维数组更快。原因可以找in the answers to this question

      【讨论】:

        【解决方案7】:

        如果你不能没有 C 构造,那么 JNI 总是存在的。

        或者您可以开发自己的 Java 派生语言(以及 VM 和优化 JIT 编译器),该语言具有多维连续内存数组的语法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-05
          • 2014-01-31
          • 2013-12-05
          • 1970-01-01
          • 1970-01-01
          • 2011-08-03
          • 1970-01-01
          相关资源
          最近更新 更多