Java Futures&快点产品

时间:2017-12-05 20:30:26

标签: java matrix vector concurrency

我试图实现一个利用并发运行得更快的点产品。我正在采用分而治之的方法,我将矢量分成越来越小的位,然后最后将所有组件加在一起。但是,我不会立即返回值,而是返回最终将包含结果的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;
  }

2 个答案:

答案 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)

等到他们完成了。