javac是否未能优化我的代码?

时间:2015-09-24 22:50:39

标签: java javac jit cpu-cache

我一直在测试cpu缓存优化,我做的一个简单测试就是在嵌套循环中求和2048x2048整数矩阵,首先我尝试使用顺序索引(总是跳转到下一个内存整数),然后使用长宽度内存访问(跳到第2048个下一个整数),我每次运行这些代码1000次,然后我得到每个代码的平均执行时间,测试是在Intel Core 2 Duo E4600 2.4 GHz上执行的,没有其他进程可以在背景上损害执行时间,结果如下:

  

顺序索引:平均值:8ms。

     

长宽度内存访问:平均值:43。

以下是源代码:

import java.util.Arrays;
import java.util.Random;

public class Main {

    public static void main(String[] args) {
        int [][] matrix = new int[2048][2048];

        long tInitial = 0;
        long tFinal = 0;

        long [] avgExecTime = new long[1000];

        for (int i = 0; i < avgExecTime.length; i++) {
            fillWithRandomNumbers(matrix);

            tInitial = System.currentTimeMillis();

            sumSequentially(matrix);

            tFinal = System.currentTimeMillis();

            avgExecTime[i] = tFinal - tInitial;
        }

        System.out.println(Arrays.stream(avgExecTime).sum() / avgExecTime.length);

        for (int i = 0; i < avgExecTime.length; i++) {
            fillWithRandomNumbers(matrix);

            tInitial = System.currentTimeMillis();

            sumRandomly(matrix);

            tFinal = System.currentTimeMillis();

            avgExecTime[i] = tFinal - tInitial;
        }

        System.out.println(Arrays.stream(avgExecTime).sum() / avgExecTime.length);
    }

    public static void fillWithRandomNumbers(int [][] matrix) {
        Random r = new Random();
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                matrix[i][j] = r.nextInt();
            }
        }
    }

    public static long sumSequentially(int [][] matrix) {
        long total = 0;

        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                total += matrix[i][j];
            }
        }

        return total;
    }

    public static long sumRandomly(int [][] matrix) {
        long total = 0;

        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                total += matrix[j][i];
            }
        }

        return total;
    }
}

我的问题是:

  1. 考虑到sumRandomly和sumSequentially具有相同的最终结果,为什么java编译器没有对sumRandomly方法进行优化?
  2. java编译器是否会考虑可能存在一个线程在发生总和时改变我的矩阵参数值,这可能会改变结果?
  3. 我已经尝试过不使用方法并在main方法上执行所有操作,结果几乎相同。

2 个答案:

答案 0 :(得分:2)

javac不会优化接近这个程度的任何地方,而JIT可能会。这使得JVM在某些方面具有优势,因为优化可以针对在运行时观察到的特定行为以及运行JVM的处理器的特定功能和怪癖进行定制。

您也在假设Java虚拟机及其对数组的处理,尤其是嵌套数组。虽然C和/或Fortran等语言处理具有矩形数据结构的多维数组,但Java不会(因此允许“不规则数组”,即int [] [],arr[0].length不需要等于{ {1}}。

这是使用对象引用的数组实现的,其中每个对象引用都指向您的arr[1].length。显然,Java确实会进行检查,如果在紧密循环中访问单个数组,这似乎更容易检测和优化,而不是多个数组上的相同索引。

答案 1 :(得分:1)

正如您在hexafraction的回答中所看到的,您需要的优化类型是不可行的。无论如何,我认为java编译器正在进行一种微小的优化,这是因为时间不同。

我发布这个答案只是为了让这些术语变得更加明显,因为我认为它们很重要。

阵列访问功能

在Java语言中,所有被调用的非原始变量都是引用,这意味着数据不能直接访问,并且可能不是按顺序存储的。

您的数据类型很长,这是原始的,但您将它们存储到数组中,这不是原始的。当您尝试访问类似matrix[i][j]的数组值时,JVM必须至少执行三次操作。找到matrix对象的引用,找到对[i]项的引用,然后找到对值[j]的引用。

正如我所说,这些值可能不会按顺序存储,所以在大多数情况下,缓存不会太多。查看代码,我们可以看到对象的两种不同形式的访问,matrix[i][j]matrix[j][i]始终i具有外部循环索引,j内部循环的索引。在这种情况下,我们可以对第一种情况进行简单的优化,因为第一个数组访问的值将在所有j循环中返回相同的值,因此编译器可以看到并说,&#34;等待那里!我不需要在每个循环中找到相同的结果。我会缓存这个!&#34;,所以你的代码将评估为:

public static long sumSequentially(int [][] matrix) {
    long total = 0;

    for (int i = 0; i < matrix.length; i++) {
        long[matrix[i].length] matrixi = matrix[i];
        for (int j = 0; j < matrixi.length; j++) {
            total += matrixi[j];
        }
    }

    return total;
}

关于第二种方法,这不是真的。 j索引会更改每个内部循环,随之改变所有循环中的第一个和第二个数组访问结果。

cpu cache

我在C中做了这个例子,因为C是CPU缓存的有效场景,我相信这是你的主要疑问。

结果是:

  

顺序求和平均值:2ms。

     

随机平均值:39毫秒。

所以,我们的情景几乎相同。 C将数据按顺序存储在数组中,我们使用多维矩阵(将char[2][2]视为类型):

+--------------+------------+
| Ram address  | Item index | 
+--------------+------------+
| 00000120000  | [0][0]     | 
+--------------+------------+
| 00000120001  | [0][1]     | 
+--------------+------------+
| 00000120002  | [1][0]     | 
+--------------+------------+
| 00000120003  | [1][1]     | 
+--------------+------------+

CPU缓存数据和指令,不需要返回RAM来获取它们。当我们按顺序访问数据时,CPU使用我们获得的数据旁边的数据填充缓存,然后当我们访问一个时,他将缓存下一个N值,问题是当我们必须访问N + 1值时,因此缓存需要进行验证,并且会有一批数据从RAM中移出。

以第二种方式访问​​数据时,您将跳转各种RAM地址然后再返回。几乎你所做的每一次跳转都会使缓存失效,缓存将再次被填充,因此你需要更多的操作,而且过程会变慢。如果你想了解更多,我前段时间看到Gallery of Processor cache effects

两种语言都有类似的行为,但出于不同的原因,我猜这是因为你感到困惑。