有什么方法可以加快Java中两个双精度数组之间的余弦相似度的计算速度吗?

时间:2018-10-08 12:02:17

标签: java arrays math

我有两个双精度数组a和b,想计算它们之间的余弦相似度。我的代码如下:

double [][] target = new double [1][65000];
double [][] compare = new double [1][65000];

double dotProduct = dot(target[0], compare[0]);
double eucledianDist = norm2(target) * norm2(compare);
double output = dotProduct / eucledianDist;

private double norm2(double[][] a){
    double sum = 0;
    for (int i = 0; i < a[0].length; i++){
        sum = sum + a[0][i] * a[0][i];
    }
    return Math.sqrt(sum);
}

private double dot(double[] a, double [] b){
    double sum = 0;
    for(int i = 0; i < a.length; i ++){
        sum += a[i] * b[i];
    }
    return sum;
}

有什么方法可以加快计算时间吗?

4 个答案:

答案 0 :(得分:4)

这里的所有代码都非常简单。另外,方法也很短。 (是的,另一个答案是正确的:要做的第一件事是减少数组上的传递总数)

从那里,您可以查看两件事:

  • 确保JIT能够尽早启动并完全内联您的方法,并将其转换为机器代码(例如,可以配置触发内联所需的循环迭代次数以及仍可以内联多长时间的方法)
  • 您的循环迭代都是独立的。因此,您可以按顺序触发多个线程,而不必按顺序计算所有迭代,而是启动多个线程,每个线程都可以工作。取决于底层硬件,这会给您的系统带来更高的负载,但也会导致结果 quick

这两种方法都需要对相应主题进行一些“挖掘”,但是这样做可能会带来很多收获。哪种解决方案可以为您带来更好的结果,这实际上取决于上下文,因此值得同时采用两种策略。

因此,基本上,您必须确保可以正确衡量执行时间(请参阅here),然后进行实验,以了解在您的设置下哪些更改可以为您带来最大的利益。

答案 1 :(得分:4)

我想您担心的是当您有大型数组时,您想要避免两次遍历它们。如在其他地方指出的那样,第一维似乎在您的函数中是多余的,因此在下面的答案中我避免了。

您可以做的是尝试将两个循环合并在一个函数中。

类似的东西:

double computeSimilarity(double[] a, double[] b) {
  //todo: you might want to check they are the same size before proceeding

  double dotProduct = 0;
  double normASum = 0; 
  double normBSum = 0;

  for(int i = 0; i < a.length; i ++) {
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
  }

  double eucledianDist = Math.sqrt(normASum) * Math.sqrt(normBSum);
  return dotProduct / eucledianDist;
}

如果您确实需要2个维度,请在每个维度上调用上述函数。因此,在您的示例中,您将其命名为computeSimilarity(target[0], compare[0]);

答案 2 :(得分:2)

为了更好地处理Stream版本,使其更具表现力和可并行性。

double computeSimilarity(final double[] a, final double[] b) {
    double normA = Math.sqrt(DoubleStream.of(a).parallel().map(x -> x * x).sum());
    double normB = Math.sqrt(DoubleStream.of(b).parallel().map(x -> x * x).sum());
    double dotProduct = IntStream.range(0, a.length).parallel()
            .mapToDouble(i -> a[i] * b[i]).sum();

    double eucledianDist = normA * normB;
    return dotProduct / eucledianDist;
}

答案 3 :(得分:0)

经典的微观优化是循环展开:复制循环主体以避免退出测试。

double computeSimilarity(double[] a, double[] b) {

  double dotProduct = 0;
  double normASum = 0; 
  double normBSum = 0;

  for(int i = 0; i + 3 < a.length; i++) {
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
      i++;
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
      i++;
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
      i++;
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
  }
  for( ; i < a.length; i ++) {
      dotProduct += a[i] * b[i];
      normASum += a[i] * a[i];
      normBSum += b[i] * b[i];
  }

  double eucledianDist = Math.sqrt(normASum) * Math.sqrt(normBSum);
  return dotProduct / eucledianDist;
}

也许将a[i]b[i]存储在临时变量中可能会产生很小的影响。