Java中多维数组的遍历性能

Java中多维数组的遍历性能,java,performance,traversal,Java,Performance,Traversal,在下面的代码和结果中,我们可以看到“Traverse2”比“Traverse1”快得多,实际上它们只遍历相同数量的元素 1.这种差异是如何发生的 2.将较长的迭代放在较短的迭代中会有更好的性能 public class TraverseTest { public static void main(String[] args) { int a[][] = new int[100][10]; System.out.println(System.cur

在下面的代码和结果中,我们可以看到“Traverse2”比“Traverse1”快得多,实际上它们只遍历相同数量的元素

1.这种差异是如何发生的

2.将较长的迭代放在较短的迭代中会有更好的性能

public class TraverseTest {

    public static void main(String[] args)
    {
        int a[][] = new int[100][10];
        System.out.println(System.currentTimeMillis());

        //Traverse1
        for(int i = 0; i < 100; i++)
        {
            for(int j = 0; j < 10; j++)
                a[i][j] = 1;
        }

        System.out.println(System.currentTimeMillis());

        //Traverse2
        for(int i = 0; i < 10; i++)
        {
            for(int j = 0; j < 100; j++)
                a[j][i] = 2;
        }

        System.out.println(System.currentTimeMillis());
    }
}
结果将是:

4888285195629

4888285846760

4888285914219

这意味着,如果我们在内部放置更长的交互,将有更好的性能。而且它似乎与缓存命中理论有一些冲突。

我的输出(使用原始代码100i/10j vs 10i/100j):

您正在使用非常糟糕的时间分辨率进行非常快速的计算

我把I和j的限制都改为1000

    int a[][] = new int[1000][1000];
    System.out.println(System.currentTimeMillis());

    //Traverse1
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[i][j] = 1;
    }

    System.out.println(System.currentTimeMillis());

    //Traverse2
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[j][i] = 2;
    }

    System.out.println(System.currentTimeMillis());
两种可能性:

  • JavaHotSpot将第二个循环更改为第一个类型或进行优化 交换i和j
  • 时间分辨率仍然不够
所以我将输出更改为System.nanoTime()

1.这种差异是如何发生的

请注意,即使只是使用了错误的时间分辨率,也会对不相等的情况进行错误的比较。第一个是连续访问,而第二个不是

假设第一个嵌套循环只是为第二个循环做准备,那么它将使您对“第二个循环快得多”的假设更加错误

不要忘记2D数组在java中是“数组的数组”。因此,最右边的索引将显示一个连续区域。第一个版本速度更快

2.将较长的迭代放在较短的迭代中会有更好的性能

for(int i=0;i<10;i++)
{
对于(int j=0;j<100;j++)
a[j][i]=2;
}
增加第一个索引的速度较慢,因为下一次迭代将以千字节的速度进行,因此无法再使用缓存线

绝对不是


首先,始终在循环中多次运行microbenchmark测试。然后您将看到这两个时间都是0,因为数组大小太小。要获得非零倍,请将数组大小增加100倍。对于遍历1,我的时间大约为32毫秒,对于遍历2,我的时间大约为250毫秒。
区别在于处理器使用高速缓存。访问顺序内存地址的速度要快得多。

我怀疑,您在这个微基准测试中看到的任何奇怪结果都是由于基准测试本身的缺陷造成的

例如:

  • 您的基准测试没有考虑“JVM预热”效应,例如JIT编译器没有立即编译为本机代码。(这只发生在代码执行了一段时间之后,JVM测量了一些使用率以帮助优化。)处理这一问题的正确方法是将整个循环放入一个运行了几次的循环中,并丢弃任何看起来“奇怪”的初始时间集。。。由于热身效应

  • 基准测试中的循环理论上可以优化掉。JIT编译器可能能够推断它们不做任何影响程序输出的工作


最后,我想提醒你,像这样的手工优化通常是个坏主意。。。除非您有令人信服的证据证明它值得您亲自优化,并且此代码确实是应用程序花费大量时间的地方。

在我看来,数组的大小也会影响结果。比如:

public class TraverseTest {

    public static void main(String[] args)
    {
        int a[][] = new int[10000][2];
        System.out.println(System.currentTimeMillis());

        //Traverse1
        for(int i = 0; i < 10000; i++)
        {
            for(int j = 0; j < 2; j++)
                a[i][j] = 1;
        }

        System.out.println(System.currentTimeMillis());

        //Traverse2
        for(int i = 0; i < 2; i++)
        {
            for(int j = 0; j < 10000; j++)
                a[j][i] = 2;
        }

        System.out.println(System.currentTimeMillis());
    }
}
公共类遍历测试{
公共静态void main(字符串[]args)
{
整数a[][]=新整数[10000][2];
System.out.println(System.currentTimeMillis());
//横穿1
对于(int i=0;i<10000;i++)
{
对于(int j=0;j<2;j++)
a[i][j]=1;
}
System.out.println(System.currentTimeMillis());
//横穿2
对于(int i=0;i<2;i++)
{
对于(int j=0;j<10000;j++)
a[j][i]=2;
}
System.out.println(System.currentTimeMillis());
}
}
Traverse1需要进行10000*3+1=30001比较,以决定是否退出迭代, 但是,遍历2只需要进行2*10001+1=20003比较


Traverse1需要1.5倍于Traverse2的比较次数。

2D数组的可能重复=数组的数组在您的代码中,外部迭代与内部迭代相同,因此时间相同,我想知道它们是否不相同,例如1000对10,是什么导致了两种不同的迭代方式的不同性能。我想你没有得到我的描述。我强调的是,外部迭代次数比内部迭代次数多,性能似乎更低。
1.这种差异是如何发生的?
答案在最后<代码>2.将较长的迭代放在较短的迭代中会有更好的性能?绝对不是。当迭代次数不多且内部与外部差异太大时,这似乎是一个因素
    int a[][] = new int[1000][1000];
    System.out.println(System.currentTimeMillis());

    //Traverse1
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[i][j] = 1;
    }

    System.out.println(System.currentTimeMillis());

    //Traverse2
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[j][i] = 2;
    }

    System.out.println(System.currentTimeMillis());
1347118210671
1347118210687 //difference is 16 ms
1347118210703 //difference is 16 ms again -_-
    int a[][] = new int[1000][1000];
    System.out.println(System.nanoTime());

    //Traverse1
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[i][j] = 1;
    }

    System.out.println(System.nanoTime());

    //Traverse2
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            a[j][i] = 2;
    }

    System.out.println(System.nanoTime());
16151040043078
16151047859993 //difference is 7800000 nanoseconds
16151061346623 //difference is 13500000 nanoseconds --->this is half speed
for(int i = 0; i < 10; i++)
    {
        for(int j = 0; j < 100; j++)
            a[j][i] = 2;
    }
public class TraverseTest {

    public static void main(String[] args)
    {
        int a[][] = new int[10000][2];
        System.out.println(System.currentTimeMillis());

        //Traverse1
        for(int i = 0; i < 10000; i++)
        {
            for(int j = 0; j < 2; j++)
                a[i][j] = 1;
        }

        System.out.println(System.currentTimeMillis());

        //Traverse2
        for(int i = 0; i < 2; i++)
        {
            for(int j = 0; j < 10000; j++)
                a[j][i] = 2;
        }

        System.out.println(System.currentTimeMillis());
    }
}