我试图实现一个利用并发运行得更快的点产品。我正在采用分而治之的方法,我将矢量分成越来越小的位,然后最后将所有组件加在一起。但是,我不会立即返回值,而是返回最终将包含结果的Future
。
这是我到目前为止的尝试:
Future<Double> dotProduct(double[] x, double[] d, int start, int end) {
if ((end-start) == 1) {
return executor.submit(() -> {
return x[start] * d[start];
});
} else if ((end-start) == 0) {
return executor.submit(() -> {
return 0.0;
});
}
int middle = (start+end)/2;
Future<Double> leftDotProduct = dotProduct(x, d, start, middle);
Future<Double> rightDotProduct = dotProduct(x, d, middle, end);
return executor.submit(() -> {
double l = leftDotProduct.get();
double r = rightDotProduct.get();
return l + r;
});
}
// Usage:
Future<Double> v = dotProduct(x, d, 0, x.length);
v.get()
它产生正确的结果,但它仍然比等效的顺序实现运行得慢。我已经测试了小型(4个条目)和大型(20,000个)条目。
我在想减速可能是由于递归调用和设置新堆栈。但是,如果情况确实如此,我甚至不确定如何重新设计算法。
对于可能导致延迟的原因以及如何改进它的任何想法都将非常感激!
编辑:
对于更多上下文,我想返回期货,因为最终我的目标是使用此方法将矩阵乘以向量:
double[] parMult(double[] x) {
if (this.getWidth() != x.length)
throw new ArithmeticException("The matrix and vector are of incompatible sizes");
// Create an array of futures that will store all the results from dot poduct
Future<Double>[] f = new Future[this.getHeight()];
for (int i=0; i<this.getHeight(); i++) {
f[i] = dotProduct(x, this.data[i]);
}
// Get the values of all futures
double[] b = new double[this.getHeight()];
try {
for (int i = 0; i < this.getHeight(); i++) {
b[i] = f[i].get();
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
答案 0 :(得分:2)
当你致电Future.get()
时,等待将来完成。所以,刚刚发生的事情是你介绍了执行器的所有开销,其中包含很多任务来调度和,因为你阻塞了,所以你强迫你的代码几乎连续运行。
您要找的是Fork+Join。累积的产品总和是fork + join模式的典型示例。
答案 1 :(得分:0)
你可以尝试Java Fork/Join framework - 它完全是为这种计算而设计的。
fork / join框架是ExecutorService接口的一个实现,可帮助您利用多个处理器。它专为可以递归分解成小块的工作而设计。目标是使用所有可用的处理能力来提高应用程序的性能。
对于您的特定情况,它可能是这样的:
class Product extends RecursiveTask<Double> {
private final double[] x;
private final double[] d;
private final int start;
private final int end;
Product(double[] x, double[] d, int start, int end) {
this.x = x;
this.d = d;
this.start = start;
this.end = end;
}
@Override
protected Double compute() {
if ((end-start) == 1) {
return x[start] * d[start];
} else if ((end-start) == 0) {
return 0.0;
}
int middle = (start+end)/2;
Product leftDotProduct = new Product(x, d, start, middle);
Product rightDotProduct = new Product(x, d, middle, end);
return leftDotProduct.fork().join() + rightDotProduct.fork().join();
}
}
然后您可以创建它并fork()
,或者只需致电compute()
Product p = new Product(x, d, 0, 999999);
经过一些测试后,它似乎比你的方法快3倍,但仍然比简单的递归解决方案慢得多。
另一种选择是使用CompletableFuture。假设你有一个递归方法:
double dotProduct(double[] x, double[] d, int start, int end) {
if ((end-start) == 1) {
return x[start] * d[start];
} else if ((end-start) == 0) {
return 0.0;
}
int middle = (start+end)/2;
double leftDotProduct = dotProduct(x, d, start, middle);
double rightDotProduct = dotProduct(x, d, middle, end);
return leftDotProduct + rightDotProduct;
}
您可以像这样创建CompletableFuture
:
CompletableFuture<Double> f = CompletableFuture.supplyAsync(() -> dotProductSingleThread(x, d, 0, 999999));
然后,如果你有很多未来,你可以使用
CompletableFuture.allOf(futures)
等到他们完成了。