I trying to learn the RecursiveTask class with the classic Fibonacci-algorithm. The algorithm works fine until fibonacci number over 17000, then a StackOverflowError is thrown. I don't know if the problem is the number of threads or that I use a cache to store the calculated numbers. I know that there exist better algorithms to calculate fibonacci numbers but this is just to learn the fork/join architecture and it's limitations. The time it takes for lower numbers (for example number 17800) takes 153 ms and the cache is then of size 13 MB.
Question: how can i make this code scale better (to calculate higher numbers) using the same algorithm?
Fibonacci code:
public class FibonacciTask extends RecursiveTask<BigInteger>{
// this theshold is used because the overhead of the architecture
// makes the process crazy slow if we try to use it on easy problems
private static final int EASYCALC = 10;
// limit of the cache
private static final int CACHELIM = 20000;
private int n;
private static Map<Integer, BigInteger> fibCache = new ConcurrentHashMap<>();
public BigInteger result;
public FibonacciTask(int x, Map<Integer, BigInteger> fibCache){
n = x;
this.fibCache = fibCache;
// calculate the first 10 numbers without threads
if(!fibCache.containsKey(EASYCALC)){
fibCache.put(EASYCALC, this.fibonacci(EASYCALC));
result = fibCache.get(x);
}
}
@Override
protected BigInteger compute() {
if(!fibCache.containsKey(n)){
FibonacciTask worker1 = new FibonacciTask(n-1, fibCache);
FibonacciTask worker2 = new FibonacciTask(n-2, fibCache);
worker1.fork(); // fork this work to new thread
result = worker2.compute().add(worker1.join());
if(n >= CACHELIM){
return result;
}
fibCache.put(n, result);
}
return fibCache.get(n);
}
// calculate without threads
public BigInteger fibonacci(int n){
if(!fibCache.containsKey(n) ){
fibCache.put(n, fibonacci(n-1).add(fibonacci(n-2)) );
}
return fibCache.get(n);
}
}
Main:
int n = 17950;
ForkJoinPool pool = new ForkJoinPool(processors);
FibonacciTask task = new FibonacciTask(n, fibCache);
pool.invoke(task);
BigInteger result = task.result;
Error output:
run:
No. of processors: 8
Exception in thread "main" java.lang.StackOverflowError
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:536)
at java.util.concurrent.ForkJoinTask.reportResult(ForkJoinTask.java:596)
at java.util.concurrent.ForkJoinTask.join(ForkJoinTask.java:640)
at java.util.concurrent.ForkJoinPool.invoke(ForkJoinPool.java:1521)
at cachedThreadedFibonacci.SmartWorker.main(SmartWorker.java:62)
Caused by: java.lang.StackOverflowError
at cachedThreadedFibonacci.FibonacciTask.compute(FibonacciTask.java:48)
at cachedThreadedFibonacci.FibonacciTask.compute(FibonacciTask.java:55)
at cachedThreadedFibonacci.FibonacciTask.compute(FibonacciTask.java:55)
... same line repeating
Edit I'm also confused because if I in Netbeans set the stack size to 2Mb (-Xss2M) then it works fine (even though the cache can be up to 17Mb with my tests)? If I set the size to 1 Mb it doesn't work again (fails at the same point as described before), what am I missing?