矩阵乘法的矩阵乘法优化

时间:2012-10-03 03:53:05

标签: c++ c algorithm optimization matrix-multiplication

我正在进行一项任务,我将转置矩阵以减少矩阵乘法运算的缓存未命中。根据我对几个同学的理解,我应该得到8倍的提升。但是,我只得到2倍......我可能做错了什么?

Full Source on GitHub

void transpose(int size, matrix m) {
    int i, j;
    for (i = 0; i < size; i++) 
        for (j = 0; j < size; j++) 
            std::swap(m.element[i][j], m.element[j][i]);
}

void mm(matrix a, matrix b, matrix result) {
    int i, j, k;
    int size = a.size;
    long long before, after;

    before = wall_clock_time();
    // Do the multiplication
    transpose(size, b); // transpose the matrix to reduce cache miss
    for (i = 0; i < size; i++)
        for (j = 0; j < size; j++) {
            int tmp = 0; // save memory writes
            for(k = 0; k < size; k++)
                tmp += a.element[i][k] * b.element[j][k];
            result.element[i][j] = tmp;
        }
    after = wall_clock_time();
    fprintf(stderr, "Matrix multiplication took %1.2f seconds\n", ((float)(after - before))/1000000000);
}

到目前为止我做得对吗?

仅供参考:我需要做的下一个优化是使用SIMD / Intel SSE3

2 个答案:

答案 0 :(得分:11)

  

到目前为止我做得对吗?

没有。你的转置有问题。在开始担心性能之前,您应该已经看过这个问题。当您进行任何类型的黑客攻击以进行优化时,总是使用天真但次优的实现作为测试。如果没有得到正确的答案,那么实现100倍加速的优化就毫无价值。

另一个有用的优化是通过引用传递。你正在传递副本。事实上,您的matrix result可能永远不会离开,因为您正在传递副本。再一次,你应该进行测试。

另一个有助于加速的优化是缓存一些指针。这仍然很慢:

for(k = 0; k < size; k++)
    tmp += a.element[i][k] * b.element[j][k];
result.element[i][j] = tmp;

优化器可能会看到指针问题,但可能没有。至少不会,如果您不使用非标准__restrict__关键字告诉编译器您的矩阵不重叠。缓存指针,因此您不必执行a.element[i]b.element[j]result.element[i]。它仍然可能有助于告诉编译器这些数组与__restrict__关键字不重叠。

<强>附录
查看代码后,需要帮助。首先是一个小评论。你不是在写C ++。你的代码是C,带有一丝C ++的暗示。您使用的是struct而不是classmalloc而不是newtypedef struct,而不仅仅是struct,C标头而不是C ++标头

由于您对struct matrix的实施,我对复制构造函数导致的缓慢的评论不正确。它不正确甚至更糟!使用隐式定义的复制构造函数与包含裸指针的类或结构一起使用火。如果有人拨打m(a, a, a_squared)来获取矩阵a的平方,则会非常严重。如果有些人希望m(a, a, a)进行a 2 的就地计算,你的工作会更加严重。

在数学上,您的代码仅涵盖矩阵乘法问题的一小部分。如果有人想将100x1000矩阵乘以1000x200矩阵怎么办?这是完全有效的,但是您的代码无法处理它,因为您的代码仅适用于方形矩阵。另一方面,你的代码会让某人将100x100矩阵乘以200x200矩阵,这没有多大意义。

从结构上讲,由于您使用了不规则的数组,您的代码几乎可以保证100%的速度。 malloc可以在内存中喷洒矩阵的行。如果矩阵在内部表示为连续数组但是被访问就好像它是NxM矩阵,那么你将获得更好的性能。 C ++为此提供了一些很好的机制。

答案 1 :(得分:3)

如果你的作业暗示你必须转置,那么,当然,你应该纠正你的转置程序。就目前而言,它会进行两次转置,完全没有转置。 j =循环不应该读

j=0; j<size; j++

j=0; j<i; j++

无需转置以避免以“错误”顺序处理其中一个因子矩阵的元素。只需交换j循环和k循环。暂且不谈任何(其他)性能调整,基本的循环结构应该是:

  for (int i=0; i<size; i++)
  {
    for (int k=0; k<size; k++)
    {
      double tmp = a[i][k];
      for (int j=0; j<size; j++)
      {
        result[i][j] += tmp * b[k][j];
      }
    }
  }