【问题标题】:How to use the multiply and accumulate intrinsics in ARM Cortex-a8?如何在 ARM Cortex-a8 中使用乘法和累加内在函数?
【发布时间】:2011-03-15 12:04:12
【问题描述】:

如何使用GCC提供的Multiply-Accumulate内在函数?

float32x4_t vmlaq_f32 (float32x4_t , float32x4_t , float32x4_t);

谁能解释我必须传递给这个函数的三个参数。我的意思是源和目标寄存器以及函数返回什么?

求救!!!

【问题讨论】:

  • GCC 文档(以及 GCC 内部函数似乎基于的内部函数的 RealView 文档)非常稀少......如果你没有得到一个体面的答案,我建议只需编译几个调用并查看输出的程序集。这应该会给你一个很好的主意(即使这是一个不太理想的方法)。

标签: c arm simd intrinsics neon


【解决方案1】:

简单地说vmla指令做了以下事情:

struct 
{
  float val[4];
} float32x4_t


float32x4_t vmla (float32x4_t a, float32x4_t b, float32x4_t c)
{
  float32x4 result;

  for (int i=0; i<4; i++)
  {
    result.val[i] =  b.val[i]*c.val[i]+a.val[i];
  }

  return result;
}

所有这些都编译成一条汇编指令:-)

您可以在 3D 图形的典型 4x4 矩阵乘法中使用此 NEON 汇编器内在函数,如下所示:

float32x4_t transform (float32x4_t * matrix, float32x4_t vector)
{
  /* in a perfect world this code would compile into just four instructions */
  float32x4_t result;

  result = vml (matrix[0], vector);
  result = vmla (result, matrix[1], vector);
  result = vmla (result, matrix[2], vector);
  result = vmla (result, matrix[3], vector);

  return result;
}

这节省了几个周期,因为您不必在乘法后将结果相加。这种加法的使用非常频繁,以至于如今乘法累加 hsa 成为主流(甚至 x86 也将它们添加到最近的一些 SSE 指令集中)。

另外值得一提的是:像这样的乘法累加运算在线性代数和 DSP(数字信号处理)应用程序中非常很常见。 ARM 非常聪明,并在 Cortex-A8 NEON-Core 中实现了快速路径。如果 VMLA 指令的第一个参数(累加器)是前面的 VML 或 VMLA 指令的结果,则此快速路径启动。我可以详细介绍,但简而言之,这样一个指令系列的运行速度是 VML / VADD / VML / VADD 系列的四倍。

看看我的简单矩阵乘法:我就是这样做的。由于这种快速路径,它的运行速度大约是使用 VML 和 ADD 而不是 VMLA 编写的实现的四倍。

【讨论】:

  • 感谢您如此详细的回复。您的回复不仅说明了该说明的功能,还说明了使用该说明的利弊。
  • 您好 Nils,我了解如何使用 NEON 指令加速矩阵乘法。它现在真的很上瘾:) 我想用 NEON 指令来做矩阵的逆,你能给我一些好的文件来解释如何使用 NEON 指令来做逆矩阵,或者你能给我任何想法,如何去做那件事?谢谢。
  • 对于矩阵逆我会在“sse matrix inverse”上进行谷歌搜索并将sse代码移植到NEON。通常的方法是通过 Cramers 规则计算小矩阵 (4x4) 的逆。
  • Nils 你能看看我这个相关的问题吗?您还可以编译我在那里发布的示例代码,并告诉我编译器是否能够为矩阵乘法生成 NEON SIMD 指令?谢谢你。 [stackoverflow.com/questions/3307821/…
  • 很好的答案。只是想为 vikramtheone 和其他人添加一个注释,以确保您确实需要矩阵求逆。伪逆通常会这样做,并且发现这是一种更快、更稳定的计算。
【解决方案2】:

Google 搜索到了 vmlaq_f32,出现了 the reference for the RVCT compiler tools。它是这样说的:

Vector multiply accumulate: vmla -> Vr[i] := Va[i] + Vb[i] * Vc[i]
...
float32x4_t vmlaq_f32 (float32x4_t a, float32x4_t b, float32x4_t c);

定义了以下类型来表示向量。 NEON 矢量数据类型根据以下模式命名: x_t 例如,int16x4_t 是一个包含四个通道的向量,每个通道包含一个有符号的 16 位整数。表 E.1 列出了矢量数据类型。

IOW,该函数的返回值将是一个包含 4 个 32 位浮点数的向量,向量的每个元素通过将bc 的对应元素相乘,再加上@ 的内容来计算987654326@.

HTH

【讨论】:

    【解决方案3】:
    result = vml (matrix[0], vector);
    result = vmla (result, matrix[1], vector);
    result = vmla (result, matrix[2], vector);
    result = vmla (result, matrix[3], vector);
    

    不过,这个序列行不通。问题是 x 分量只累积由矩阵行调制的 x,可以表示为:

    result.x = vector.x * (matrix[0][0] + matrix[1][0] + matrix[2][0] + matrix[3][0]);
    

    ...

    正确的顺序是:

    result = vml (matrix[0], vector.xxxx);
    result = vmla(result, matrix[1], vector.yyyy);
    

    ...

    NEON 和 SSE 没有内置的字段选择(这将需要 8 位指令编码,每个向量寄存器)。例如,GLSL/HLSL 确实有这种设施,所以大多数 GPU 也有。

    实现此目的的替代方法是:

    result.x = dp4(vector, matrix[0]);
    result.y = dp4(vector, matrix[1]);
    

    ... // 当然,矩阵将被转置以产生相同的结果

    mul,madd,madd,madd 序列通常是首选,因为它不需要目标寄存器字段的写掩码。

    否则代码看起来不错。 =)

    【讨论】:

      猜你喜欢
      • 2011-05-11
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多