无疑,FMA 指令是脱胎于 AMD 的 SSE5 指令集,这在上一篇文章关于《XOP 指令集架构简介》已经介绍过,SSE5 提出了 multiply-add/subtract 类运算,AMD 方面 SSE5 指令集最后形成了 XOP 指令集(提供整数 multiply-add/subtract 处理)和 FMA4 指令集(提供 multiply-add/subtract 浮点数处理)。
Intel 正式化了 FMA 指令集并随着 AVX 指令集一起发布,AMD 随后废除了 SSE5 指令集,也正式使用 FMA 指令名称。Intel 和 AMD 的 FMA 指令集在规格和功能上完全是相同的(兼容),可是在指令名称上存在一些分歧。
事实上 FMA 指令集存在两种区别:
一般地 FMA 指令指的是 FMA3 指令,也就是由 Intel 发布的 FMA 指令集,虽然存在这样的区别,但是实质上这种版本是完全一样的,下面我们来探讨一下这些区别和实质。
Intel 阵营中的 FMA 指令,在指令 mnemonic(助记符)方面采用了数字 123 的修饰方案,mnemonic 的数字意义有两方面:
一方面表示 operands 的寻址方式,如下:
二方面根据数字的排列顺序来表示 operands 的职责作用,如下:
下面看看这 3 条指令例子:
它们实质上是同一条指令的不同表现形式。
|
vfmadd132pd ymm0, ymm1, ymm2
|
在排列顺序上,第 1 个 operand 排在前面,因此它是被乘数,第 3 个 operand 排在中间,因此它是乘数(也就是 ymm2 是乘数的原因),第 2 个 operand 排在最后,因此它是加数(即:ymm1)
vfmadd213pd ymm0, ymm1, ymm2
|
在排列顺序上,第 2 个 operand 排在前面,因此它是被乘数(即:ymm1),第 1 个 operand 排在中间,因此它是乘数(即:ymm0),第 3 个 operand 排在最后,因此它是加数(即:ymm2)
vfmadd231pd ymm0, ymm1, ymm2
|
在排列顺序上,第 2 个 operand 排在前面,因此它是被乘数(即:ymm1),第 3 个 operand 排在中间,因此它是乘数(即:ymm2),第 1 个 operand 排在最后,因此它是加数(即:ymm0)
这里我们看到一个规则是:FMA 指令的 operands 寻址是固定的,同时第 3 个操作数不可能是被乘数,也就是说不可能是 memory 操作数,我们可以通过改变操作数的顺序,使用两条不同的指令形式达到同样的结果,像下面的例子:
FMA4 是 AMD 方面的 FMA 实现方案,支持 4 个操作数,与 Intel 的 FMA 设计思路存在分歧。
AMD 的方案是,实现了一个指令 mnemonic,存在不同的 operands 排列,下面指令例子:
这条指令功能对应 Intel 的 vfmadd132pd(vfmadd213pd 及 vfmadd231pd)系列指令,执行的运算是:
由于 FMA4 支持 4 个操作数,目标操作数与源操作数可以是不同的寄存器,FMA4 指令的 mnemonic 显得简单明了。
FMA 指令执行的是:fused-multiply-add(融合乘加)类运算,包括一些其他的变种版本。
这类运算执行的是:

这类运算执行的操作是:

这类运算中,乘运算融合了加减交互运算,怎样个加减并互,看看下面的式子:
|
double a[4], b[4], c[4]; // packed double |
以处理的数据是 pd(packed double)为例, 指令 vfmaddsubpd 所做运算正如上面的式子,依次低 double 做 fused-multiply-subtract 运算,接着高 double 做 fused-multiply-add 运算。

这类运算和上一类运算相似,所不同的是 subtract 和 add 运算的次序,它所做的操作如下式子:
|
double a[4], b[4], c[4]; // packed double
|
可以看出这里是:依次低 double 做 fused-multiply-add 运算,接着高 double 做 fused-multiply-subtract 运算。

这类运算是将乘积取负,然后相加,它所做的操作是:

和上一类运算所不同的是:融合 subtract 运算,它所做的操作是:

可以看到,整个 FMA 指令集包括这 6 类运算,处理的数据类型包括:packed double,packed single,scalar double 以及 scalar single,它们能处理 128 位及 256 位的数据大小。