【问题标题】:Efficient Embedded Fixed Point 2x2 Matrix Multiplication in ARM Cortex-M4 C codeARM Cortex-M4 C 代码中的高效嵌入式定点 2x2 矩阵乘法
【发布时间】:2021-05-06 16:54:36
【问题描述】:

我正在尝试在 C 代码中实现非常高效的 2x2 矩阵乘法,以便在 ARM Cortex-M4 中进行操作。该函数接受 3 个指向 2x2 数组的指针,2 个用于相乘的输入和一个由 using 函数传递的输出缓冲区。这是我到目前为止所拥有的......

static inline void multiply_2x2_2x2(int16_t a[2][2], int16_t b[2][2], int32_t c[2][2])
{
  int32_t a00a01, a10a11, b00b01, b01b11;

  a00a01 = a[0][0] | a[0][1]<<16;
  b00b10 = b[0][0] | b[1][0]<<16;
  b01b11 = b[0][1] | b[1][1]<<16;
  c[0][0] = __SMUAD(a00a01, b00b10);
  c[0][1] = __SMUAD(a00a01, b01b11);

  a10a11 = a[1][0] | a[1][1]<<16;
  c[1][0] = __SMUAD(a10a11, b00b10);
  c[1][1] = __SMUAD(a10a11, b01b11);
}

基本上,我的策略是使用 ARM Cortex-M4 __SMUAD() 函数来进行实际的乘法累加。但这需要我提前构建输入 a00a01、a10a11、b00b10 和 b01b11。我的问题是,鉴于 C 数组在内存中应该是连续的,是否有更有效的方式将数据直接传递给函数而无需中间变量?第二个问题,我是不是想多了,我应该让编译器完成它的工作,因为它比我更聪明吗?我经常这样做。

谢谢!

【问题讨论】:

  • 我非常怀疑没有staticvoid inline 是你想要的。而不要使用inline。我认为函数是small enough,你可以在汇编中编写它。
  • 这些 CMSIS 例程会有帮助吗? keil.com/pack/doc/CMSIS/DSP/html/group__MatrixMult.html 另外,因为你有一个 M4 - FPU 有很多寄存器......将两个源加载到寄存器中并使用 FPU,然后保存。我不知道转换是否会影响您的时间安排。我不会为 2D 索引而烦恼...只要抓住你想要的 int。
  • @aMike:并非每个 Cortex-M4 都有 FPU。

标签: c embedded cortex-m


【解决方案1】:

您可以打破严格的别名规则,使用int16_t*int32_t* 类型转换将矩阵行直接加载到32 位寄存器中。诸如a00a01 = a[0][0] | a[0][1]&lt;&lt;16 之类的表达式只是从RAM 中获取一些连续位并将它们排列到寄存器中的其他连续位中。请参阅您的编译器手册以了解该标志以禁用其严格的别名假设,并使强制转换安全可用。

您也可以通过首先以转置格式生成 b 来避免将矩阵列转置到寄存器中。

了解编译器并了解它比您更聪明的情况的最佳方法是反汇编其结果并将指令序列与您的意图进行比较。

【讨论】:

    【解决方案2】:

    第一个主要问题是some_signed_int &lt;&lt; 16 为负数调用未定义的行为。所以你到处都是错误。然后对两个int16_t 进行按位或运算,其中任一为负也不一定形成有效的int32_t。你真的需要这个标志还是可以放弃它?

    ARM 示例使用 unsigned int,它又包含 2x int16_t 的原始二进制形式。这也是你真正想要的。

    此外,SMUAD 将哪个 16 位字放在哪里似乎并不重要。所以a[0][0] | a[0][1]&lt;&lt;16; 只是用来在内存中不必要地交换数据。它会使无法很好地优化此类代码的编译器感到困惑。当然,轮班等总是非常快,但这是毫无意义的开销。

    (正如有人指出的那样,用纯汇编程序编写整个事情可能要容易得多,而无需考虑所有 C 类型规则和未定义的行为。)

    为避免所有这些问题,您可以定义自己的联合类型:

    typedef union
    {
      int16_t  i16 [2][2];
      uint32_t u32 [2];
    } mat2x2_t;
    
    • u32[0] 对应于 i16[0][0]i16[0][1]
    • u32[1] 对应于 i16[1][0]i16[1][1]

    C 实际上让您可以在这些类型之间“输入双关语”(与 C++ 不同)。工会也避开了脆弱的严格别名规则。

    然后该函数可以变成类似于此伪代码的内容:

    static uint32_t mat_mul16 (mat2x2_t a, mat2x2_t b)
    {
       uint32_t c0 = __SMUAD(a.u32[0], b.u32[0]);
       ...
    }
    

    根据SMUAD 指令,假设每一行应该给出 2x 有符号的 16 次乘法。

    至于与默认的MUL相比,这是否真的带来了革命性的性能提升,我有点怀疑。反汇编并计算 CPU 滴答数。

    我是不是想多了,我应该让编译器完成它的工作,因为它比我更聪明?

    很可能 :) 旧的经验法则:基准测试,然后仅在您实际发现性能瓶颈时手动优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-27
      • 2019-09-22
      • 2013-06-13
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多