Recursive version of Java function is slower than iterative on first call, but faster after. Why is this?

谁都会走 提交于 2019-12-10 18:27:47

问题


For an assignment I'm currently trying to measure the performance (space/time) difference between an iterative solution to the matrix chain problem and a recursive one.

The gist of the problem and the solution I'm using for the iterative version can be found here: http://www.geeksforgeeks.org/dynamic-programming-set-8-matrix-chain-multiplication/

I'm running a given input through both functions 10 times, measuring the space and time performance of each function. The very interesting thing is that while the recursive solution runs much slower than the iterative solution on the first call it's performance is much better on successive calls it is much faster. The functions are not making use of any class-global variables other than one for counting memory usage. Why is this occurring? Is it something the compiler is doing or am I missing something obvious?

Note: I know my way of measuring memory is wrong, planning on changing it.

Main: Initializes Array and passes it to run functions

    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: Runs both functions 2 * n times, measuring space and time and printing results at the end

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));
}

solveRecursive: bootstrap for doRecursion

private static int solveRecursive(int[] s, boolean measureMemory){

    memoryUsage = 0;
    maxMemory = 0;

    int n = s.length - 1;
    int[][]  m = new int[n][n];
    int result;

    if (measureMemory){
        memoryUsage += MemoryUtil.deepMemoryUsageOf(n);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(s);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(m);
        result = doRecursion(0, n - 1, m,  s);
        memoryUsage += MemoryUtil.deepMemoryUsageOf(result);
        System.out.println("Memory Used: " + memoryUsage);
    }
    else
    {
        result = doRecursion(0, n - 1, m,  s);
    }
    return result;
}

doRecursion: solves the function recursively

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];
}

solveIterative: Solves the problem iteratively

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];
}

Output:

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]

回答1:


The problem is that your test runs too short. JIT has not enough time to optimize the methods well enough.

Try repeating the test at least 200 times (instead of 15) and you'll see the difference.

Note that JIT compilation does not happen just once. Methods can be recompiled several times as JVM collects more runtime statistics. You've hit the situation where solveRecursive survived more levels of optimization than solveIterative.


In this answer I've described how JIT decides to compile a method. Basically there are two main compilation triggers: the method invocation threshold and the backedge threshold (i.e. loop iteration counter).

Note that those two methods have different compilation triggers:

  • solveRecursive does more calls => it is compiled when invocation threshold is reached;
  • solveIterative runs more loops => it is compiled when backedge threshold is reached.

These thresholds are not equal, and it happens that on a short distance solveRecursive is compiled earlier. But as soon as solveIterative is optimized, it starts to perform even better.

There is also a trick to make solveIterative compiled earlier: move the innermost loop for (k = i; k < j; k++) to a separate method. Yes, it may sound strange, but JIT is better in compiling several small methods instead of compiling one big method. Smaller methods are easier to understand and to optimize not only for humans, but also for computers :)




回答2:


It is absolutely 100% normal for Java code to get faster after you use it more. That's more or less the whole point of the JIT compiler -- to optimize at runtime code that's getting used more heavily.



来源:https://stackoverflow.com/questions/39904701/recursive-version-of-java-function-is-slower-than-iterative-on-first-call-but-f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!