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