The answer to your question is quite simple:
Just do it the usual way, i.e., unwinding the stack yourself with return
s. Why? Because this is not as slow as you might think. Unless the computation in your recursion is highly trivial and the stack depth is very very high, the returning will never influence the runtime of your algorithm noticeably.
Basically, you have the following options:
- If possible, transform your algorithm to an iterative one.
- Transform your algorithm to be end recursive and hope that the VM will reuse the stack frame. Then, returning out of recursion is virtually equal to one simple return.
- Throw an exception. However, this will be even slower than returning, because the stack trace has to be built, which ultimatly traverses the stack, too. Also the stack has to be unwound to check for
catch
es. You win nothing.
The former two options are viable, but not always possible. But honestly, don't think about it. Returning out of a deep stack is not the part that makes your algorithm slow. If you have an algorithm with a very deep recursion, then you have a problem anyway (stack overflow, costs of the recursive calls) and should consider to rewrite your algorithm. If the stack depth is low, then this is a non-issue anyway.
Here is a simple Java test program to show you what I mean:
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public class DeepRecursion {
private long returnStartTime;
public int recurse(int i) {
int r = (int)Math.sqrt((double)i); // Some non-trivial computation
if(i == 0) {
returnStartTime = System.currentTimeMillis();
return r;
}
return r + recurse(i-1);
}
public void testRecursion(int i, PrintStream p) {
long startTime = System.currentTimeMillis();
int result = recurse(i);
long endTime = System.currentTimeMillis();
p.println(
"Recursion depth: " + i + " Result: " + result + "\n" +
"Time for recursion" + (returnStartTime - startTime) + "\n" +
"Time for return " + (endTime - returnStartTime) + "\n"
);
}
public void testIteration(int i, PrintStream p) {
long startTime = System.currentTimeMillis();
int result = 0;
for(int k = 0; k <= i; k++) {
int r = (int)Math.sqrt((double)k); // Some non-trivial computation
result += r;
}
long endTime = System.currentTimeMillis();
p.println("Iteration length: " + i + " Result: " + result + "\nTime: " + (endTime - startTime) );
}
public static void main(String[] args) {
DeepRecursion r = new DeepRecursion();
PrintStream nullStream = new PrintStream(new ByteArrayOutputStream());
for(int k = 0; k < 10; k++) {
// Test stack depths from 1024 to 33554432
for(int i = 10; i < 26; i++) {
r.testIteration(1 << i, k == 9 ? System.out : nullStream);
r.testRecursion(1 << i, k == 9 ? System.out : nullStream);
}
}
}
}
It computes a recursive function which will have a stack depth equal to the input parameter. The function computes a square root in each stack fram e to simulate some non-trivial computation. It also computes the same function in an iterative way. To warm up the JIT, the program is first executed 9 times without printing the result; only the tenth result is printed. Here are my results (I had to increase the stack size to 1 gigabyte with -Xss1g
. Here are the results from my machine:
Iteration length: 1024 Result: 21360
Time for iteration: 0
Recursion depth: 1024 Result: 21360
Time for recursion 0
Time for return 0
Iteration length: 2048 Result: 60810
Time for iteration: 0
Recursion depth: 2048 Result: 60810
Time for recursion 0
Time for return 0
Iteration length: 4096 Result: 172768
Time for iteration: 0
Recursion depth: 4096 Result: 172768
Time for recursion 0
Time for return 0
Iteration length: 8192 Result: 490305
Time for iteration: 0
Recursion depth: 8192 Result: 490305
Time for recursion 0
Time for return 0
Iteration length: 16384 Result: 1390016
Time for iteration: 0
Recursion depth: 16384 Result: 1390016
Time for recursion 0
Time for return 0
Iteration length: 32768 Result: 3938198
Time for iteration: 0
Recursion depth: 32768 Result: 3938198
Time for recursion 0
Time for return 0
Iteration length: 65536 Result: 11152256
Time for iteration: 0
Recursion depth: 65536 Result: 11152256
Time for recursion 1
Time for return 0
Iteration length: 131072 Result: 31570201
Time for iteration: 0
Recursion depth: 131072 Result: 31570201
Time for recursion 1
Time for return 0
Iteration length: 262144 Result: 89347840
Time for iteration: 2
Recursion depth: 262144 Result: 89347840
Time for recursion 1
Time for return 1
Iteration length: 524288 Result: 252821886
Time for iteration: 2
Recursion depth: 524288 Result: 252821886
Time for recursion 4
Time for return 1
Iteration length: 1048576 Result: 715304448
Time for iteration: 5
Recursion depth: 1048576 Result: 715304448
Time for recursion 7
Time for return 3
Iteration length: 2097152 Result: 2023619820
Time for iteration: 9
Recursion depth: 2097152 Result: 2023619820
Time for recursion 14
Time for return 4
Iteration length: 4194304 Result: 1429560320
Time for iteration: 18
Recursion depth: 4194304 Result: 1429560320
Time for recursion 29
Time for return 12
Iteration length: 8388608 Result: -986724456
Time for iteration: 36
Recursion depth: 8388608 Result: -986724456
Time for recursion 56
Time for return 28
Iteration length: 16777216 Result: -1440040960
Time for iteration: 72
Recursion depth: 16777216 Result: -1440040960
Time for recursion 112
Time for return 61
Iteration length: 33554432 Result: 712898096
Time for iteration: 145
Recursion depth: 33554432 Result: 712898096
Time for recursion 223
Time for return 123
As you see, it takes 3 millisecond to return from a stack of depth one million. Larger stack sizes cause longer times, possibly due to the stack no longer fitting into L3 cache. However, if you need such large stacks, you have a problem anyway, as described above. Running Java with 1 gigabyte max stack size is not the best idea. Any stack size below 131072 is not even measurable in milliseconds. In a sane algorithm, the stack should be much smaller than this, so you should always be fine.
As you see, the fastest solution is the iterative one, so if a very deep recursion is too slow, avoid it completely instead of only skipping the return.
Conclusion
If recursion is too slow for you, get rid of it completely. Just skipping the return won't make a big difference.