【问题标题】:How to make my dot product method in Java faster or more efficient?如何使我在 Java 中的点积方法更快或更高效?
【发布时间】:2016-11-05 03:21:34
【问题描述】:

我有一个小的 Java 方法,用于在输入向量和矩阵之间执行点积。代码如下:

    public void calcOutput() {
    outputs = new float[output];
    float sum = 0F;

    for(int j = 0; j < output; j++) {
        for(int i = 0; i < input; i++) {
            sum += inputs[i] * weights[j][i];
        }

        outputs[j] = sum;
    }
}

基本上,这应该做的是获取我的输入向量“输入”并使用我命名为“权重”的矩阵执行点积。然后将输出放置在输出向量“输出”中。

我怎样才能使这更快或更高效?如果有帮助,我的权重矩阵也不需要是矩阵。我只需要一种方法来轻松访问相应的索引。

谢谢

【问题讨论】:

  • 您是否对这段代码进行了基准测试?为什么你认为它可以更有效率?
  • @cricket_007 是的,考虑到大的输入和输出数字以及大的权重矩阵,它运行大约 30-40 毫秒。
  • 您可以通过反转外部循环和内部循环来使其速度稍微加快,以减少对inputs 向量的访问次数。以后有时间我会写答案的。
  • 多次调用此方法时使用相同的weights 值吗?
  • 使用多个累加器可能会有所帮助,这是为了获得良好性能必须做的事情,但 JIT 编译器可能会害怕这样做,因为它会稍微改变结果。

标签: java optimization matrix dot-product


【解决方案1】:

不,没有比这更好的了。这是您可以实现的最简单的方法,该算法遵循良好的内存缓存方法,即外部循环遵循数组的外部索引,内部循环遍历一个子数组中的元素。

也许对内部数组使用临时变量会有所帮助,但我想 JIT 会处理这个问题。

另外,还有一个错误,sum 变量应该在外循环范围内,而不是方法范围内。它需要在外循环的每次迭代中重新设置:

for(int j = 0; j < output; j++) {
    // NOTE the line:
    float sum = 0;
    // and the reference to inner array:
    byte[] row = weights[j];
    for(int i = 0; i < input; i++) {
        sum += inputs[i] * row[i];
    }

    outputs[j] = sum;
}

【讨论】:

    【解决方案2】:

    有几种方法比编写一个普通的点积要好得多。幼稚的实现将由 C2 向量化,但顺序归约阶段非常慢,以至于抵消了向量化乘法的好处。现在在 Java (JDK10) 中,您可以做的最好的事情是使用部分和来展开以打破数据依赖关系。 C2 将发出标量代码,但它会使用一些流水线,并且您最多可以获得 4 次触发器/周期。

    float s0 = 0f;
    float s1 = 0f;
    float s2 = 0f;
    float s3 = 0f;
    float s4 = 0f;
    float s5 = 0f;
    float s6 = 0f;
    float s7 = 0f;
    for (int i = 0; i < size; i += 8) {
      s0 = Math.fma(left[i + 0],  right[i + 0], s0);
      s1 = Math.fma(left[i + 1],  right[i + 1], s1);
      s2 = Math.fma(left[i + 2],  right[i + 2], s2);
      s3 = Math.fma(left[i + 3],  right[i + 3], s3);
      s4 = Math.fma(left[i + 4],  right[i + 4], s4);
      s5 = Math.fma(left[i + 5],  right[i + 5], s5);
      s6 = Math.fma(left[i + 6],  right[i + 6], s6);
      s7 = Math.fma(left[i + 7],  right[i + 7], s7);
    }
    return s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7;
    

    为了尽可能快地进行,您需要使用累加器进行显式矢量化。可以使用 Project Panama Vector API 编写这样的代码。

    var sum1 = YMM_FLOAT.zero();
    var sum2 = YMM_FLOAT.zero();
    var sum3 = YMM_FLOAT.zero();
    var sum4 = YMM_FLOAT.zero();
    int width = YMM_FLOAT.length();
    for (int i = 0; i < size; i += width * 4) {
      sum1 = YMM_FLOAT.fromArray(left, i).fma(YMM_FLOAT.fromArray(right, i), sum1);
      sum2 = YMM_FLOAT.fromArray(left, i + width).fma(YMM_FLOAT.fromArray(right, i + width), sum2);
      sum3 = YMM_FLOAT.fromArray(left, i + width * 2).fma(YMM_FLOAT.fromArray(right, i + width * 2), sum3);
      sum4 = YMM_FLOAT.fromArray(left, i + width * 3).fma(YMM_FLOAT.fromArray(right, i + width * 3), sum4);
    }
    return sum1.addAll() + sum2.addAll() + sum3.addAll() + sum4.addAll();
    

    请参阅blog post 了解基准和深入分析。

    【讨论】:

      【解决方案3】:

      这就是我要做的。通过颠倒外部和内部循环,您可以减少inputs 数组中的查找次数。此外,您不需要 sum 变量 - 您可以直接在 outputs 数组中进行添加。

          float[] outputs = new float[output];
      
          for(int i = 0; i < input; i++) {
              float inputsI = inputs[i];
              for(int j = 0; j < output; j++) {
                  outputs[j] += inputsI * weights[j][i];
              }
      
          }
      

      我希望这只会快一点。在几乎所有现实世界的应用程序中,不值得担心像这样的微小优化。

      【讨论】:

        猜你喜欢
        • 2013-08-05
        • 2019-11-22
        • 1970-01-01
        • 1970-01-01
        • 2018-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-03
        相关资源
        最近更新 更多