许多年前,我在Microsoft Xbox 360部门工作。 我们考虑过发布一个新的控制台,并决定如果该控制台可以运行上一代控制台中的游戏,那就太好了。
模拟总是很困难,但是如果您的公司老板不断更改中央处理器的类型,则难度会更大。 第一个Xbox(不要与Xbox One混淆)使用了x86 CPU。 在第二个Xbox中,对不起,Xbox
360使用了PowerPC处理器。 第三个Xbox,即Xbox
One ,使用了x86 / x64 CPU。 不同
ISA之间的这种飞跃并没有简化我们的生活。
我参加了一个团队的工作,该团队教Xbox 360模拟第一个Xbox的许多游戏,即在PowerPC上模拟x86,为此我获得了
“忍者模拟”的称号。 然后,我被要求研究在x64 CPU上模拟Xbox 360 PowerPC CPU的问题。 我会事先说,我还没有找到令人满意的解决方案。
FMA!= MMA
困扰我的事情之一是融合乘法或
FMA指令。 这些指令在输入处接收了三个参数,将前两个参数相乘,然后将第三个参数相加。 融合意味着直到操作结束才进行舍入。 即,以完全精确度执行乘法,之后执行加法,然后才将结果舍入为最终答案。
为了用一个具体的例子展示这一点,让我们想象一下我们使用十进制浮点数和两个精度数字。 想象一下此计算,以函数形式显示:
FMA(8.1e1, 2.9e1, 4.1e1), 8.1e1 * 2.9e1 + 4.1e1, 81 * 29 + 41
81*29
等于
2349
,加上41后我们得到
2390
。 四舍五入到两位数,我们得到
2400
或
2.4e3
。
如果没有FMA,则首先必须执行乘法运算,得到
2349
,它将把精度取整到两位数并给出
2300 (2.3e3)
。 然后我们加
41
,得到
2341
,
它将再次四舍五入,我们将得到最终结果
2300 (2.3e3)
,它比FMA答案的准确性差。
注1: FMA(a,b, -a*b)
计算FMA(a,b, -a*b)
的误差,这实际上很酷。
注2:注1的副作用之一是,如果计算机自动生成FMA指令,则x = a * b – a * b
可能不会返回零。
因此,显然,FMA比单独的乘法和加法指令提供了更准确的结果。 我们不会深入探讨,但是我们会同意,如果我们需要将两个数字相乘然后再加上第三个数字,那么FMA将比其替代方法更为准确。 另外,FMA指令的等待时间通常比乘法指令和加法指令短。 在Xbox 360 CPU中,延迟和FMA处理速度等于
fmul或
fadd的延迟和FMA处理速度,因此使用FMA代替
fmul,然后使用从属
fadd可以将延迟减少一半。
FMA仿真
Xbox 360编译器
始终生成矢量和标量的
FMA指令 。 我们不确定我们选择的x64处理器是否支持这些指令,因此快速准确地模拟它们至关重要。 使我们对这些指令的仿真变得理想是必要的,因为根据我以前的仿真浮点计算经验,我知道“相当接近”的结果会导致角色掉落地板,汽车飞出世界等等。
那么,如果x64 CPU不支持FMA指令,那么
需要什么来完美地模拟它们呢?
幸运的是,游戏中的绝大多数浮点计算都是以浮点精度(32位)执行的,我可以在FMA仿真中愉快地使用双精度指令(64位)。
似乎使用具有双精度的计算来模拟具有浮点精度的FMA指令应该很简单(
讲述人的声音:并非如此;浮点运算从未如此简单 )。 浮点型的精度为24位,双精度型的精度为53位。 这意味着,如果将传入的浮点数转换为精度双精度(无损转换),则可以执行乘法而不会出错。 也就是说,要存储完全准确的结果,仅48位的准确度就足够了,而我们拥有更多的准确度,那就是一切都井井有条。
然后,我们需要做加法。 仅以浮点格式取第二项,将其转换为双精度,然后将其加到乘法结果中就足够了。 由于舍入不会在乘法过程中发生,而是仅在加法之后执行,因此完全可以模拟FMA。 我们的逻辑是完美的。 您可以宣布胜利并返回家园。
胜利是如此接近...
但这不起作用。 或至少对于某些传入数据失败。 思考一下为什么会发生这种情况。
通话保持音乐声...
发生故障是因为,按照FMA的定义,乘法和加法是完全精确地执行的,然后结果以精确浮点取整。 我们
几乎设法实现了这一目标。
发生乘法而不进行舍入,然后在加法之后执行舍入。 这
类似于我们正在尝试做的事情。 但是加法后的舍入以
双精度完成。 之后,我们需要以浮点精度保存结果,这就是为什么再次发生舍入的原因。
小熊维尼
双舍入 。
很难清楚地说明这一点,因此让我们回到十进制浮点格式,其中单精度为两位小数,双精度为四位。 假设我们计算
FMA(8.1e1, 2.9e1, 9.9e-1)
或
81 * 29 + .99
。
该表达式的确切答案是
2349.99
或
2.34999e3
。 四舍五入到精度单位(两位数),我们得到
2.3e3
。 让我们看看尝试模拟这些计算时出了什么问题。
当我们将
81
和
29
乘以double的精度时,得到
2349
。 到目前为止一切顺利。
然后我们加
.99
并得到
2349.99
。 一切都还好。
该结果四舍五入为double的精度,我们得到
2350 (2.350e3)
。 哎呀
我们将其四舍五入为精度单,并根据IEEE
四舍五入规则
将其四舍五入,即使我们得到
2400 (2.4e3)
。 这是错误的答案。 与FMA指令返回的正确取整结果相比,它的错误略大。
您可以指出问题出在IEEE环境规则中,直到最近为止。 但是,无论您选择哪种舍入规则,总会出现双舍入返回与真实FMA不同的结果的情况。
一切如何结束?
我无法找到一个完全令人满意的解决方案。
我在Xbox One发行之前很久就离开了Xbox团队,从那时起,我就没有对控制台进行过多的关注,所以我不知道他们做出了什么决定。 现代x64 CPU具有FMA指令,可以完美地模拟此类操作。 您还可以通过某种方式使用x87数学协处理器来模拟FMA-我不记得我研究这个问题时得出的结论。 也许开发人员只是简单地认为结果相当接近并且可以使用。