在查看Fork/Join Tutorial之后,我创建了一个用于计算大因子的类:
public class ForkFactorial extends RecursiveTask<BigInteger> {
final int end;
final int start;
private static final int THRESHOLD = 10;
public ForkFactorial(int n) {
this(1, n + 1);
}
private ForkFactorial(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected BigInteger compute() {
if (end - start < THRESHOLD) {
return computeDirectly();
} else {
int mid = (start + end) / 2;
ForkFactorial lower = new ForkFactorial(start, mid);
lower.fork();
ForkFactorial upper = new ForkFactorial(mid, end);
BigInteger upperVal = upper.compute();
return lower.join().multiply(upperVal);
}
}
private BigInteger computeDirectly() {
BigInteger val = BigInteger.ONE;
BigInteger mult = BigInteger.valueOf(start);
for (int iter = start; iter < end; iter++, mult = mult.add(BigInteger.ONE)) {
val = val.multiply(mult);
}
return val;
}
}
我的问题是如何确定我细分任务的门槛?我发现page on fork/join parallelism表示:
实施算法时要考虑的主要事项之一 使用fork / join parallelism选择确定的阈值 任务是否将执行顺序计算而不是 分叉并行子任务。
如果阈值太大,则程序可能无法创建 足够的任务,以充分利用可用的 处理器/核。
如果阈值太小,那么任务创建的开销和 管理可能会变得很重要。
一般来说,需要进行一些实验才能找到 适当的阈值。
那么我需要做些什么样的实验才能确定阈值?
答案 0 :(得分:4)
PigeonHole估计:设置任意阈值,计算计算时间。 并根据它增加和减少阈值,看看你的计算时间是否有所改善,直到你看到没有通过降低阈值来改善时为止。
答案 1 :(得分:4)
选择阈值取决于许多因素:
实际计算应花费合理的时间。如果你要求一个数组并且数组很小,那么按顺序执行它可能会更好。如果数组长度为16M,则将其拆分为较小的部分并进行并行处理应该是值得的。试试吧,看看。
处理器数量应该足够。 Doug Lea曾用16+处理器记录他的框架,以使其值得。即使将数组分成两半并在两个线程上运行,吞吐量也会增加1.3%。现在您必须考虑拆分/加入开销。尝试在许多配置上运行,看看你得到了什么。
并发请求的数量应该很少。如果您有N个处理器和8(N)个并发请求,那么每个请求使用一个线程通常对吞吐量更有效。这里的逻辑很简单。如果您有N个处理器可用,并且相应地拆分了您的工作,但是您前面还有数百个其他任务,那么分裂的重点是什么?
这就是实验手段。
不幸的是,这个框架没有问责制。无法在每个线程上看到负载。 deques的高水位标记。已处理的请求总数遇到的错误等等。
祝你好运。答案 2 :(得分:2)
请注意,算术不是BigInteger的常量时间,它与输入的长度成正比。尽管在Q / A部分中引用的futureboy实现记录了它(期望)在不同情况下实现的内容,但每个操作的实际复杂性并不容易在hand。
在决定如何将问题划分为更小的块以及确定特定块是否值得再次划分时,使工作估计功能正确非常重要。
使用实验来确定阈值时,您需要注意不要只是对问题空间的一个角进行基准测试。
答案 3 :(得分:1)
据我了解,此实验是一项优化,因此只有在有需要时才应用。
您可以尝试不同的拆分策略 - 即可以通过两个相等的部分或估计的乘法成本(取决于整数小数长度)进行分割。
对于每种策略,您可以测试尽可能多的阈值,以全面了解您的策略。如果你的CPU资源有限,那么你可以测试,即每5或10日。因此,根据我的经验,这里的第一个重要事项是全面了解算法的执行情况。