【问题标题】:How to perform a fast array multiplication?如何执行快速数组乘法?
【发布时间】:2013-11-11 22:16:35
【问题描述】:

我有两个大小相同的数组:

A = [a1, a2, a3]
B = [b1; b2; b3]

我需要执行一个数组乘法来构建以下矩阵:

            |a1|
M = A * B = |a2| * |b1 b2 b3|  //M31 * M13 ==> M33 and M13 * M31 ==> M11.  Mnk: Matrix with n lines and k columns.
            |a3|


    | a1b1 a1b2 a1b3 |
M = | a2b1 a2b2 a2b3 |
    | a3b1 a3b2 a3b3 |

完成这项任务的最快算法是什么?

更详细一点:我需要使用 8086 指令集来完成这项工作,但这里我更愿意接收 C 代码中的算法。

【问题讨论】:

  • 好问题。但是你尝试过什么?
  • 另外,有什么数据可以保证帮助简化一些计算吗? (即,如果它是一个典型的 2d 旋转/缩放/平移矩阵,那么有一些 1 和 0 将很容易优化)
  • 你为什么不做显而易见的事情呢? AFAIK 8086 没有任何缓存可言,因此任何不明显方式(更好地使用缓存)的通常动机都不适用。
  • 这是你可以使用库的东西,还是需要你自己做的某种练习?
  • @MOHAMED 我尝试使用简单的 for 循环 O(n^2)...

标签: c arrays algorithm assembly matrix-multiplication


【解决方案1】:

如果您的数组很大并且您想尽可能快地将它们相乘,那么您应该看看BLAS 库。

【讨论】:

    【解决方案2】:

    研究 BLAS 和 LAPACK。这些都是高度优化的。除非您有理由避免使用图书馆,否则不要重新发明轮子。这两个都有 C API。

    【讨论】:

      【解决方案3】:

      看起来像Matrix Multiplication Algorithm 更准确地说,我认为您正在寻找efficient 方式。

      矩阵相乘的一般方法是 O(n^3),但如果采用有效的方法,您将得到 O(n^2.807)。是否值得您花时间实施有效的方法?我不知道,但你必须评估它。

      如果您只有一维数组,那么唯一的方法是双循环,在这种情况下,您正在查看运行时间 O(n^2)。 想出它不应该那么复杂:

      for(int i = 0; i < A.length; i++)
      {
          for(int j = 0; j < B.length; j++)
          {
              C[i][j] = A[i] * B[j];
          }
      }
      

      【讨论】:

      • 倒置 j 和 i 索引更有效:cplusplus.com/doc/tutorial/arrays(该页面适用于 c++,但 c 也是如此)对于 A 和 B 数组无关紧要,但是对于 C 来说,至少在 A 和 B 长度变大时是这样。
      • @Sebastien,不,它的方式很好 - 内部循环将按列遍历 C,这意味着连续的内存位置,展示空间局部性并在大多数现代缓存和预取器上获得更好的性能
      • 我认为情况正好相反。检查我的链接了吗?这似乎表明内存在行中是连续的。此外,可以通过使用宏定义的向量到数组计算来完全避免这个问题。
      【解决方案4】:

      我建议您以最简单/最愚蠢的方式编写代码(使用 2 个 fors 和 if/else),以便让您的编译器决定可以/不可以进行哪些优化(不要忘记设置标志到-O3)。这比尝试通过反转矩阵访问等来优化缓存/内存访问来尝试优化代码要好。如果您想进行真正的优化,请找到更好的算法,否则代码很简单。

      【讨论】:

        【解决方案5】:

        Wikipedia article on Matrix Multiplication 告诉您所需的一切。

        在这种情况下,你不会比 O(n^2) 更快。​​

        一般情况下,矩阵乘法的最佳性能是O(n ^log2(7))
        (大约是 O(n^2.8)

        【讨论】:

        • Man 它的 O(n^3) 朴素矩阵乘法,而分治法有 O(n^2.8),有一个更好的算法,大约是 O(n^2.3),但并不是“实际上”最好的,因为没有足够大的矩阵或足够的计算能力来处理这些矩阵以使该算法高效(该死的常数)
        【解决方案6】:

        对于这种简单的 3x3 情况和编译器优化,最简单的O(N^2) 算法可能会足够快。如果有人想进行基准测试,我们非常欢迎您:

        #include <stdio.h>
        
        void lean_and_mean_mul(int a[3], int b[3], int out[3][3])
        {
            int i, j;
            for (i = 0; i < 3; i++)
            {
                for (j = 0; j < 3; j++)
                {
                    out[i][j] = a[i] * b[j];
                }
            }
        }
        
        int main(void)
        {
            int a[] = { 1, 2, 3 };
            int b[] = { 4, 5, 6 };
            int out[3][3];
            lean_and_mean_mul(a, b, out);
            int i, j;
            for (i = 0; i < 3; i++)
            {
                for (j = 0; j < 3; j++)
                {
                    printf("%d ", out[i][j]);
                }
                printf("\n");
            }
            return 0;
        } 
        

        让我们看看gcc -O2 -S生成的lean_and_mean_mul()的汇编:

            xorl    %eax, %eax         
        .L2:
            movl    (%rsi), %ecx
            imull   (%rdi), %ecx
            movl    %ecx, (%rdx,%rax)
            movl    4(%rsi), %ecx
            imull   (%rdi), %ecx
            movl    %ecx, 4(%rdx,%rax)
            movl    8(%rsi), %ecx
            imull   (%rdi), %ecx
            addq    $4, %rdi
            movl    %ecx, 8(%rdx,%rax)
            addq    $12, %rax
            cmpq    $36, %rax
            jne .L2
            rep
            ret
        

        注意编译器决定unroll 一个循环。

        使用gcc -O3 编译器展开两个循环。 Code。它真的很快,因为它根本没有跳跃。

        【讨论】:

          猜你喜欢
          • 2011-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-12
          • 2012-02-05
          • 2017-01-25
          • 2012-09-30
          相关资源
          最近更新 更多