Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java函数的递归版本在第一次调用时比迭代版本慢,但在第二次调用后更快。为什么会这样?_Java_Performance_Recursion_Jvm_Jit - Fatal编程技术网

Java函数的递归版本在第一次调用时比迭代版本慢,但在第二次调用后更快。为什么会这样?

Java函数的递归版本在第一次调用时比迭代版本慢,但在第二次调用后更快。为什么会这样?,java,performance,recursion,jvm,jit,Java,Performance,Recursion,Jvm,Jit,对于一项作业,我目前正试图衡量矩阵链问题的迭代解和递归解之间的性能(空间/时间)差异 我在迭代版本中使用的问题和解决方案的要点可以在这里找到: 我通过两个函数运行给定的输入10次,测量每个函数的空间和时间性能。非常有趣的是,尽管递归解决方案在第一次调用时运行得比迭代解决方案慢得多,但在连续调用时,它的性能要好得多,速度要快得多。这些函数不使用任何类全局变量,只使用一个来计算内存使用量。为什么会发生这种情况?这是编译器正在做的事情还是我遗漏了一些明显的东西 注:我知道我测量记忆的方法是错误的,计划

对于一项作业,我目前正试图衡量矩阵链问题的迭代解和递归解之间的性能(空间/时间)差异

我在迭代版本中使用的问题和解决方案的要点可以在这里找到:

我通过两个函数运行给定的输入10次,测量每个函数的空间和时间性能。非常有趣的是,尽管递归解决方案在第一次调用时运行得比迭代解决方案慢得多,但在连续调用时,它的性能要好得多,速度要快得多。这些函数不使用任何类全局变量,只使用一个来计算内存使用量。为什么会发生这种情况?这是编译器正在做的事情还是我遗漏了一些明显的东西

注:我知道我测量记忆的方法是错误的,计划改变它

Main:初始化数组并将其传递给运行函数

    public static void main(String[] args) {

    int s[] = new int[] {30,35,15,5,10,100,25,56,78,55,23};
    runFunctions(s, 15);

}
runFunctions:运行两个函数2*n次,测量空间和时间,最后打印结果

private static void runFunctions(int[]arr , int n){
    final Runtime rt = Runtime.getRuntime();

    long iterativeTime[] = new long [n],
         iterativeSpace[] = new long [n],
         recursiveSpace[] = new long [n],
         recursiveTime[] = new long [n];

    long startTime, stopTime, elapsedTime, res1, res2;

    for (int i = 0; i <n; i++){

        System.out.println("Measuring Running Time");

        //measure running time of iterative
        startTime = System.nanoTime();
        res1 = solveIterative(arr, false);
        stopTime = System.nanoTime();
        elapsedTime = stopTime - startTime;
        iterativeTime[i] = elapsedTime;

        //measure running time of recursive
        startTime = System.nanoTime();
        res2 = solveRecursive(arr, false);
        stopTime = System.nanoTime();
        elapsedTime = stopTime - startTime;
        recursiveTime[i] = elapsedTime;

        System.out.println("Measuring Space");

        //measure space usage of iterative
        rt.gc();
        res1 = solveIterative(arr, true);
        iterativeSpace[i] = memoryUsage;

        //measure space usage of recursive
        rt.gc();
        res2 = solveRecursive(arr, true);
        recursiveSpace[i] = memoryUsage;
        rt.gc();

        if (res1 != res2){
            System.out.println("Error! Results do not match! Iterative Result: " + res1 + " Recursive Result: " + res2);
        }
    }

    System.out.println("Time Iterative: " + Arrays.toString(iterativeTime));
    System.out.println("Time Recursive: " + Arrays.toString(recursiveTime));
    System.out.println("Space Iterative: " + Arrays.toString(iterativeSpace));
    System.out.println("Space Recursive: " + Arrays.toString(recursiveSpace));
}
doRecursion:递归地求解函数

private static int doRecursion(int i, int j, int[][] m, int s[]){

    if (m[i][j] != 0){
        return m[i][j];
    }
    if (i == j){
        return 0;
    }
    else
    {
        m[i][j] = Integer.MAX_VALUE / 3;
        for (int k = i; k <= j - 1; k++){
            int q = doRecursion(i, k, m, s) + doRecursion(k + 1, j, m, s) + (s[i] * s[k + 1] * s[j + 1]);
            if (q < m[i][j]){
                m[i][j] = q;
            }
        }
    }
    return m[i][j];
}
Java代码在您更多地使用它之后变得更快,这绝对是100%正常的。这或多或少就是JIT编译器的全部要点——在运行时优化使用率越来越高的代码。

问题在于测试运行时间太短。JIT没有足够的时间来充分优化方法。 试着重复测试至少200次(而不是15次),你会看到不同之处

请注意,JIT编译不会只发生一次。当JVM收集更多的运行时统计信息时,可以多次重新编译方法。您遇到的情况是,
solveRecursive
solveIterative
经受了更多级别的优化


我已经描述了JIT如何决定编译一个方法。基本上有两个主要的编译触发器:方法调用阈值和后缘阈值(即循环迭代计数器)

请注意,这两个方法具有不同的编译触发器:

  • solveRecursive
    执行更多调用=>当达到调用阈值时编译它
  • solveIterative
    运行更多循环=>当达到后缘阈值时编译它
这些阈值不相等,而且在短距离内,solveRecursive被更早地编译。但只要
solveIterative
得到优化,它就开始表现得更好


还有一个技巧可以使
solveIterative
更早编译:将(k=i;k移动到一个单独的方法。是的,这听起来很奇怪,但是JIT在编译几个小方法时比编译一个大方法要好。更小的方法更容易理解和优化,不仅适用于人类,也适用于计算机:)

欢迎来到动态优化、JIT编译Java运行时的世界。它可能不仅仅是JIT编译。这可能是一些其他昂贵的启动开销,如类加载。如果关闭JIT编译(使用VM arg
-Djava.compiler=NONE
),您可能会得到更一致的并行比较。不管怎样,您的第一次迭代都可能是一个异常值。有趣的是,它是一个一致的异常值,每次我运行它时,递归调用都非常可怕,但在连续调用上比迭代版本要好得多。有趣的是,它比迭代版本改进了很多。谢谢你关于禁用JIT的提示,我会确保做到这一点,以确保准确性。这取决于你所说的“准确度”是什么意思。如果你想衡量哪一个在实际的程序使用中会更快,JIT应该是开启的。没有JIT的测量将得到一致的结果,但不一定是有意义的结果。然而,这并不能回答为什么
solveRecursive
最终比
solveIterative
更快的问题(这有点违反直觉)。JIT编译应该使这两种方法更快。什么比什么更好并不完全可以预测。为了确保代码是用
-XX:+printcomilation
编译的,测试应该运行足够长的时间,以便您看到所有方法都已编译+1.
private static int doRecursion(int i, int j, int[][] m, int s[]){

    if (m[i][j] != 0){
        return m[i][j];
    }
    if (i == j){
        return 0;
    }
    else
    {
        m[i][j] = Integer.MAX_VALUE / 3;
        for (int k = i; k <= j - 1; k++){
            int q = doRecursion(i, k, m, s) + doRecursion(k + 1, j, m, s) + (s[i] * s[k + 1] * s[j + 1]);
            if (q < m[i][j]){
                m[i][j] = q;
            }
        }
    }
    return m[i][j];
}
private static int solveIterative(int[] s, boolean measureMemory) {
    memoryUsage = 0;
    maxMemory = 0;
    int n = s.length - 1;
    int i = 0, j = 0, k= 0, v = 0;
    int[][]  m = new int[n][n];
    for (int len = 2; len <= n; len++) {
        for (i = 0; i + len <= n; i++) {
            j = i + len - 1;
            m[i][j] = Integer.MAX_VALUE;
            for (k = i; k < j; k++) {
                v = m[i][k] + m[k + 1][j] + s[i] * s[k + 1] * s[j + 1];
                if (m[i][j] > v) {
                    m[i][j] = v;
                }
            }
        }
    }

    if (measureMemory){
        memoryUsage += MemoryUtil.deepMemoryUsageOf(n);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(m);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(i);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(j);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(k);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(v);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(s);

        System.out.println("Memory Used: " + memoryUsage);
    }

    return m[0][n - 1];
}
Time Iterative: [35605, 12039, 20492, 17674, 17674, 12295, 11782, 19467, 16906, 18442, 21004, 19980, 18955, 12039, 13832]
Time Recursive: [79918, 4611, 8453, 6916, 6660, 6660, 4354, 6916, 18699, 7428, 13576, 5635, 4867, 3330, 3586]
Space Iterative: [760, 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, 760]
Space Recursive: [712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712]