Java 2D数组填充-无害优化导致了严重的减速

Java 2D数组填充-无害优化导致了严重的减速,java,arrays,performance,multidimensional-array,benchmarking,Java,Arrays,Performance,Multidimensional Array,Benchmarking,我试图通过计算两个元素(相对于主对角线相反)的每一个和来优化每个元素的索引和填充正方形二维Java数组。但是我没有加速,或者至少没有可比的性能,而是让代码慢了23倍 我的代码: @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OperationsPerInvocation(ArrayFill.N * ArrayFill.N) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class A

我试图通过计算两个元素(相对于主对角线相反)的每一个和来优化每个元素的索引和填充正方形二维Java数组。但是我没有加速,或者至少没有可比的性能,而是让代码慢了23倍

我的代码:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(ArrayFill.N * ArrayFill.N)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ArrayFill {
    public static final int N = 8189;
    public int[][] g;

    @Setup
    public void setup() { g = new int[N][N]; }

    @GenerateMicroBenchmark
    public int simple(ArrayFill state) {
        int[][] g = state.g;
        for(int i = 0; i < g.length; i++) {
            for(int j = 0; j < g[i].length; j++) {
                g[i][j] = i + j;
            }
        }
        return g[g.length - 1][g[g.length - 1].length - 1];
    }

    @GenerateMicroBenchmark
    public int optimized(ArrayFill state) {
        int[][] g = state.g;
        for(int i = 0; i < g.length; i++) {
            for(int j = 0; j <= i; j++) {
                g[j][i] = g[i][j] = i + j;
            }
        }
        return g[g.length - 1][g[g.length - 1].length - 1];
    }
}

问题是:
如何解释如此巨大的性能下降

另一方面,Java版本是1.8.0-ea-b124,64位3.2 GHz AMD处理器,基准测试在一个线程中执行。

旁注:您的“优化”版本可能不会更快,即使我们将所有可能的问题都放在一边。现代CPU中有多种资源,其中一种资源饱和可能会阻止您进行任何改进。我的意思是:速度可能是内存限制的,在一次迭代中尝试写两倍的速度可能根本不会改变任何东西

我可以看到三个可能的原因:

  • 您的访问模式可能强制执行绑定检查。在“简单”循环中,它们可以被明显消除,而在“优化”循环中,只有当阵列是正方形时,它们才能被消除。是的,但是这些信息只在方法之外可用(此外,不同的代码可能会改变它!)

  • “优化”循环中的内存局部性不好。它访问基本上是随机的内存位置,因为Java中没有什么像2D数组(只有一个数组数组,
    newint[N][N]
    是其快捷方式)。按列迭代时,您只使用每个加载缓存线中的一个
    int
    ,即64个字节中的4个字节

  • 内存预取器可能与您的访问模式有问题。包含8189*8189*4字节的数组太大,无法放入任何缓存。现代CPU有一个预取器,当它发现一个常规的访问模式时,可以预先加载缓存线。预取器的功能差别很大。这在这里可能无关紧要,因为您只是在写,但我不确定是否有可能写入尚未获取的缓存线

我想内存位置是主要原因: 我添加了一个“反向”方法,它的工作原理非常简单,但是

g[j][i] = i + j;
而不是

g[i][j] = i + j;
这种“无害”的变化是一种性能下降:

Benchmark                                Mode   Samples         Mean   Mean error    Units
o.o.j.s.ArrayFillBenchmark.optimized     avgt        20       10.484        0.048    ns/op
o.o.j.s.ArrayFillBenchmark.reversed      avgt        20       20.989        0.294    ns/op
o.o.j.s.ArrayFillBenchmark.simple        avgt        20        0.693        0.003    ns/op

我写了一个比“简单”更快的版本。但是,我不知道为什么它会更快。下面是代码:

class A {
  public static void main(String[] args) {
    int n = 8009;

    long st, en;

    // one
    int gg[][] = new int[n][n];
    st = System.nanoTime();
    for(int i = 0; i < n; i++) {
      for(int j = 0; j < n; j++) {
        gg[i][j] = i + j; 
      }
    }
    en = System.nanoTime();

    System.out.println("\nOne time " + (en - st)/1000000.d + " msc");

    // two
    int g[][] = new int[n][n];
    st = System.nanoTime();
    int odd = (n%2), l=n-odd;
    for(int i = 0; i < l; ++i) {
      int t0, t1;   
      int a0[] = g[t0 = i];
      int a1[] = g[t1 = ++i];
      for(int j = 0; j < n; ++j) {
        a0[j] = t0 + j;
        a1[j] = t1 + j;
      }
    }
    if(odd != 0)
    {
      int i = n-1;
      int a[] = g[i];
      for(int j = 0; j < n; ++j) {
        a[j] = i + j;
      }
    }
    en = System.nanoTime();
    System.out.println("\nOptimized time " + (en - st)/1000000.d + " msc");

    int r = g[0][0]
    //  + gg[0][0]
    ;
    System.out.println("\nZZZZ = " + r);

  }
}
有人能解释一下为什么它更快吗?

图片:

int[][]==值数组的数组

int[]==值数组

A类{
公共静态void main(字符串[]args){
int n=5000;
int g[][]=新的int[n][n];
朗街,恩;
//一个
st=系统.nanoTime();
对于(int i=0;i
两次71.998012 msc

两次551.664166 msc

3次63.74851 msc

4次57.215167 msc

另外,我不是java规范=)


我明白了,您为第二次运行分配了一个新阵列,但您是否尝试更改“未优化”和“优化”运行的顺序菲克托

我改变了它们的顺序,并对其进行了一些优化:

class A {
  public static void main(String[] args) {
    int n = 8009;
    double q1, q2;
    long st, en;

    // two
    int g[][] = new int[n][n];
    st = System.nanoTime();
    int odd = (n%2), l=n-odd;
    for(int i = 0; i < l; ++i) {
      int t0, t1;   
      int a0[] = g[t0 = i];
      int a1[] = g[t1 = ++i];
      for(int j = 0; j < n; ++j, ++t0, ++t1) {
        a0[j] = t0;
        a1[j] = t1;
      }
    }
    if(odd != 0)
    {
      int i = n-1;
      int a[] = g[i];
      for(int j = 0; j < n; ++j, ++i) {
        a[j] = i;
      }
    }
    en = System.nanoTime();
    System.out.println("Optimized time " + (q1=(en - st)/1000000.d) + " msc");

    // one
    int gg[][] = new int[n][n];
    st = System.nanoTime();
    for(int i = 0; i < n; i++) {
      for(int j = 0; j < n; j++) {
        gg[i][j] = i + j; 
      }
    }
    en = System.nanoTime();

    System.out.println("One time " + (q2=(en - st)/1000000.d) + " msc");

    System.out.println("1 - T1/T2 = " + (1 - q1/q2));

  }
}

您可能想读一下:@Mysticial我不相信缓存争用会导致x23速度下降如果将
8189
推离
8192
,性能会发生什么变化?在您的“优化”版本中,您减少了比较和迭代的时间,但增加了(大量)阵列访问,包括,这三种中最贵的一种。请参阅:@Mysticial我希望内部数组正好占用8页,但显然我忘了应用
-XX:+UseCompressedOops
,它们占用了8页+4字节:(注意,我没有从“大”中读取任何内容)数组内存处于紧循环中,因此你的第2点和第3点是不相关的。我说得不对?@leventov:你说得不对,但CPU可能必须这样做。好吧,缓存和内存之间的所有通信都使用缓存线作为最小的单元。CPU可以请求特定的地址,并首先获取缓存线的相应部分,但总是这样获取一整行。我想,编写并不是更灵活。我可能没有抓住你评论的重点。你确实可以访问
g[j][I]
这在理想情况下意味着像
4*(8189*I+j)
这样的地址。这已经够糟糕了,但正如我所写的,Java中没有2D数组,所以你基本上可以访问一个随机位置。maaartinus是正确的,a在(几乎)所有情况下,“存储”到内存本质上都是一种增强的读取操作—您获取行,并保证所有权以保持缓存一致性。我知道,您为第二次运行分配了一个新数组,但您是否尝试更改“未优化”和“优化”的顺序“运行?是的,它有点不同,但第二个版本更好。它很好。”
One time 165.177848 msc

Optimized time 99.536178 msc

ZZZZ = 0
class A {
  public static void main(String[] args) {
    int n = 8009;
    double q1, q2;
    long st, en;

    // two
    int g[][] = new int[n][n];
    st = System.nanoTime();
    int odd = (n%2), l=n-odd;
    for(int i = 0; i < l; ++i) {
      int t0, t1;   
      int a0[] = g[t0 = i];
      int a1[] = g[t1 = ++i];
      for(int j = 0; j < n; ++j, ++t0, ++t1) {
        a0[j] = t0;
        a1[j] = t1;
      }
    }
    if(odd != 0)
    {
      int i = n-1;
      int a[] = g[i];
      for(int j = 0; j < n; ++j, ++i) {
        a[j] = i;
      }
    }
    en = System.nanoTime();
    System.out.println("Optimized time " + (q1=(en - st)/1000000.d) + " msc");

    // one
    int gg[][] = new int[n][n];
    st = System.nanoTime();
    for(int i = 0; i < n; i++) {
      for(int j = 0; j < n; j++) {
        gg[i][j] = i + j; 
      }
    }
    en = System.nanoTime();

    System.out.println("One time " + (q2=(en - st)/1000000.d) + " msc");

    System.out.println("1 - T1/T2 = " + (1 - q1/q2));

  }
}
Optimized time 99.360293 msc
One time 162.23607 msc
1 - T1/T2 = 0.3875573231033026