Java 爪哇期货及;快速点积
我正在尝试实现一个利用并发性来加快运行速度的点产品。我将采用一种分而治之的方法,将向量分割成越来越小的比特,最后将所有分量相加。但是,我没有立即返回值,而是返回最终将包含结果的Java 爪哇期货及;快速点积,java,matrix,vector,concurrency,Java,Matrix,Vector,Concurrency,我正在尝试实现一个利用并发性来加快运行速度的点产品。我将采用一种分而治之的方法,将向量分割成越来越小的比特,最后将所有分量相加。但是,我没有立即返回值,而是返回最终将包含结果的Futures 以下是我迄今为止的尝试: Future<Double> dotProduct(double[] x, double[] d, int start, int end) { if ((end-start) == 1) { return executor.submit(() -&g
Future
s
以下是我迄今为止的尝试:
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()
未来点积(双[]x,双[]d,整数开始,整数结束){
如果((结束-开始)==1){
返回执行者。提交(()->{
返回x[start]*d[start];
});
}否则如果((结束-开始)==0){
返回执行者。提交(()->{
返回0.0;
});
}
中间整数=(开始+结束)/2;
未来的leftDotProduct=dotProduct(x,d,start,middle);
未来rightDotProduct=dotProduct(x、d、中间、末端);
返回执行者。提交(()->{
double l=leftDotProduct.get();
double r=rightDotProduct.get();
返回l+r;
});
}
//用法:
未来v=点积(x,d,0,x.length);
v、 得到()
它产生正确的结果,但仍然比等效的顺序实现运行得慢。我已经测试了小(4个条目)和大(20000个)条目
我在想,速度减慢可能是由于递归调用和设置新堆栈造成的。但如果真是这样,我甚至不知道如何重新设计算法
如果您对造成延迟的原因以及如何改善延迟有任何想法,我们将不胜感激
编辑: 关于更多内容,我想返回futures,因为最终我的目标是使用此方法将矩阵乘以向量:
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;
}
double[]parMult(double[]x){
if(this.getWidth()!=x.length)
抛出新的算术异常(“矩阵和向量大小不兼容”);
//创建一个未来数组,该数组将存储dotpoduct的所有结果
Future[]f=newfuture[this.getHeight()];
对于(int i=0;i当您调用Future.get()
时,您等待未来的完成。因此,刚才发生的事情是,您引入了执行器的所有开销,需要分派大量任务,并且由于阻塞,您迫使代码几乎连续运行
你要找的是。乘积的累积和是fork+join模式的一个经典例子。你可以试试——它正是为这种计算而设计的
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();
}
}
CompletableFuture<Double> f = CompletableFuture.supplyAsync(() -> dotProductSingleThread(x, d, 0, 999999));
经过一些测试,它似乎比您的方法快3倍左右,但仍然比简单的递归解决方案慢得多
另一个选项是使用。假设您有一个递归方法:
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
:
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();
}
}
CompletableFuture<Double> f = CompletableFuture.supplyAsync(() -> dotProductSingleThread(x, d, 0, 999999));
等待所有这些任务完成。通常,将此类工作负载分成比您拥有的更多的部分没有任何价值(hyper-)内核。任何进一步的操作都只会增加开销。您可能感兴趣:@OliverCharlesworth-有意义。我希望将线程池限制为仅4个线程可能会带来适度的改进,但情况更糟(ave顺序:16ms,ave并发:8399ms)20k完全不足以并行化。当你进入高100和高百万时,你会看到改进。我维护了一个数据并行产品,可以进行矩阵和向量处理,如果你想知道我是如何做到的:不应该调用.get()执行器线程中的
方法使其成为非阻塞线程?感谢另一个链接-我会检查它!它不会阻塞调用submit()
的线程,但请注意,您不死锁的唯一原因(除非您有一个无限的线程池)是因为您“自下而上”安排了工作。(如果您在提交的任务中对dotProduct()
发出递归调用,您可能会看到死锁)。有效的做法是将工作分阶段进行,然后对每个组合步骤进行同步。请注意,不需要进行同步,因为:a+(b+c)==(a+b)+c
。因此使用.get()进行等待
不太必要。如果改用fork+join,您可能会看到工作窃取功能的好处,因此如果b
的解析速度恰好快于a
,则处理b
的线程将很早开始处理c
。我想我要追求的好处是(a+b)+(c+d)
而不是((a+b)+c)+d)
,因为在前者中,我可以同时计算(a+b)=x
和(c+d)=y
,然后x+y
。在后者中,我必须连续计算(a+b)=x
然后x+c=y
然后y+d
。