问题
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