本章讨论与基于 SPARC 的工作站中所使用的浮点单元有关的问题,并介绍一种用来确定哪个代码生成标志最适合特定工作站的方法。
本节列出许多 SPARC 处理器,并介绍它们所支持的指令集和异常处理功能。
下表列出了由最新 SPARC 系统使用的硬件浮点实现:
|
|
尽管不受支持,但在 Oracle Solaris 10 Update 10 或早期版本的 Oracle Solaris 上使用 Oracle Solaris Studio 12.4 编译的程序通常在支持早期版本的 Oracle Solaris Studio 的较旧 SPARC 系统上运行。要创建可执行文件以在这些平台上进行测试,可尝试使用以下选项进行编译:
–m32 –xarch=generic –xchip=generic
对于支持的解决方案,可在您要使用的早期版本的 Oracle Solaris 上编译,然后使用该 Oracle Solaris 版本支持的最新版本的 Oracle Solaris Studio 进行编译。
上表中的最后一列显示要用来为每个 FPU 获得最快代码的编译器标志。这些标志控制代码生成的两个独立属性:–xarch 标志确定编译器可以使用的指令集,–xchip 标志确定编译器在调度代码时将针对处理器的性能特点进行的假设。使用缺省 –xarch 或显式 –xarch=sparc 编译的程序可在上面列出的任何基于 SPARC 的系统上运行,尽管它可能无法充分利用较新处理器的功能。同样,如果某系统基于 SPARC 且支持用特定 –xchip 指定指令集,则用特定 –xarch 值编译的程序可在该系统上运行,但是,如果在未指定处理器的系统上运行时,速度会非常慢。
UltraSPARC I、UltraSPARC II、UltraSPARC IIe、UltraSPARC IIi、UltraSPARC III、UltraSPARC IIIi、UltraSPARC IV 和 UltraSPARC IV+ 浮点单元实现在《SPARC Architecture Manual Version 9》中定义的浮点指令集(四倍精度指令除外);特别是,它们提供 32 个双精度浮点寄存器。为了允许编译器使用所有这些功能,请使用 –xarch=sparc 进行编译。这些处理器还提供了标准指令集的扩展。使用这些 –xarch 值,可连续生成这些附加指令:
sparcvis
sparcvis2
sparcvis2
sparc4
sparc5
这些附加指令很少由编译器自动生成,但可在汇编代码中使用它们。
可以使用 –xtarget 宏选项同时指定 –xarch 和 –xchip 选项。–xtarget 标志只是扩展到 –xarch、–xchip 和 –xcache 标志的适当组合。缺省的代码生成选项是 -xtarget=generic。有关更多信息,包括 –xarch、–xchip 和 –xtarget 值的完整列表,请参见 cc(1)、CC(1) 和 f95(1) 手册页,以及Oracle Solaris Studio 12.4:Fortran 用户指南 、Oracle Solaris Studio 12.4:C 用户指南 和Oracle Solaris Studio 12.4:C++ 用户指南 编译器手册。
对于所有 SPARC 浮点单元来说,无论它们实现哪种版本的 SPARC 体系结构,它们都提供一个浮点状态寄存器 (floating-point status register, FSR),该寄存器中包含与 FPU 相关的状态位和控制位。所有实现延迟浮点陷阱的 SPARC FPU 都提供一个浮点队列 (floating-point queue, FQ),该队列中包含有关当前执行的浮点指令的信息。FSR 可由用户软件访问,以检测已经发生的浮点异常,并控制舍入方向、捕获和非标准的算术模式。FQ 由操作系统的内核使用,以便处理浮点陷阱;用户软件通常不可访问它。
软件通过 STFSR 和 LDFSR 指令来访问浮点状态寄存器,这两个指令的作用分别是将 FSR 存储在内存中和从内存中加载它。在 SPARC 汇编语言中,这些指令按如下方式编写:
st %fsr, [addr] ! store FSR at specified address ld [addr], %fsr ! load FSR from specified address
内联模板文件 libm.il 所在的目录中包含随 Sun Studio 编译器提供的库,该文件包含 STFSR 和 LDFSR 指令的用法示例。
下图显示浮点状态寄存器中位字段的布局。
图 B-1 SPARC 浮点状态寄存器
在第 7 版本和第 8 版本的 SPARC 体系结构中,FSR 占用如上所示的 32 位。在第 9 版本中,FSR 扩展到 64 位,其中的低 32 位与该图相匹配;高 32 位大部分不使用,只包含了三个附加浮点条件代码字段。
在图中,res 是指保留位,ver 是用来标识 FPU 版本的只读字段,ftt 和 qne 由系统用来处理浮点陷阱。其余字段将在下表中介绍
|
RM 字段保留两个为浮点运算指定舍入方向的位。NS 位在实现它的 SPARC FPU 上启用非标准的算术模式;在其他系统上,该位将被忽略。fcc 字段保留由浮点比较指令生成的浮点条件代码并且由分支和条件移动运算使用。最后,TEM、aexc 和 cexc 字段包含五个位,这些位针对五个 IEEE 754 浮点异常中的每一个异常控制捕获功能并记录已发生和当前异常标志。下表对这些字段进行了细分。
|
(上面的符号 NV、OF、UF、DZ 和 NX 分别代表无效运算、溢出、下溢、除以零和不精确异常。)
在大多数情况下,SPARC 的浮点单元可以在硬件中完成指令的执行且无需软件的支持。但是,在以下四种情况下,硬件将无法成功完成浮点指令:
浮点单元被禁用。
指令未被硬件所实现,如,任何 SPARC FPU 上的四倍精度指令。
硬件无法为指令操作数传送正确的结果。
指令将导致一个 IEEE 754 浮点异常,并且已经启用了该异常的陷阱。
在每种情况下,最初的响应都是相同的:进程被捕获到系统内核,由系统内核确定捕获的原因并采取相应的措施。术语“捕获”是指正常控制流的中断。在前三种情况下,内核在软件中模拟捕获指令。请注意,模拟指令也可能导致已启用陷阱的异常。
在上述的前三种情况下,如果模拟指令未导致已启用陷阱的 IEEE 浮点异常,则说明内核已完成该指令。如果该指令是浮点比较指令,内核将更新条件代码以反映结果;如果该指令是算术运算,内核将相应的结果传送给目标寄存器。内核还会更新当前的异常标志以反映由该指令引发的任何(未捕获)异常,并将这些异常与已发生异常标志进行“或”运算。内核随后安排继续从捕获点执行该进程。
当硬件执行的或者内核软件模拟的某个指令导致一个已启用陷阱的 IEEE 浮点异常,则该指令将无法完成。目标寄存器、浮点条件代码和已发生异常标志保持不变,当前异常标志设置为反映导致了该陷阱的特定异常,内核向该进程发送一个 SIGFPE 信号。
下面的伪代码概括了浮点陷阱的处理。请注意,aexc 字段通常只能由软件清除。
FPop provokes a trap; if trap type is fp_disabled, unimplemented_FPop, or unfinished_FPop then emulate FPop; texc = all IEEE exceptions generated by FPop; if (texc and TEM) = 0 then f[rd] = fp_result; // if fpop is an arithmetic op fcc = fcc_result; // if fpop is a compare cexc = texc; aexc = (aexc or texc); else cexc = trapped IEEE exception generated by FPop; throw SIGFPE;
内核必须模拟许多浮点指令时,程序的性能将严重下降。出现该问题的相对频率可能取决于多个因素(包括陷阱类型)。
在正常情况下,每个进程中只应出现一次 fp_disabled 陷阱。系统内核禁用浮点单元,直至某个进程被首次启动时为止,因此由该进程执行的第一个浮点运算将导致陷阱。处理完该陷阱之后,内核会启用浮点单元,并使其在以后的进程中保持启用状态。(可以针对整个系统禁用浮点单元,但是不建议这样做,而且只在出于内核或硬件调试目的时才这样做。)
无论浮点单元何时遇到它未实现的指令,都会出现 unimplemented_FPop 陷阱。由于目前大多数的 SPARC 浮点单元都至少实现由《SPARC Architecture Manual Version 8》定义的指令集(四倍精度指令除外),而且 Oracle Solaris Studio 编译器不生成四倍精度指令,所以不应在使用 –xarch=sparc 编译的大多数系统上出现这种类型的陷阱。
其余两个捕获类型 unfinished_FPop 和捕获的 IEEE 异常通常与涉及 NaN、无穷大和次正规数的特殊计算情况相关。
当某个浮点指令遇到一个已启用陷阱的 IEEE 浮点异常时,该指令将无法完成。相反,系统会向该进程发送一个 SIGFPE 信号。如果该进程已经建立了 SIGFPE 信号处理程序,则该处理程序将被调用,否则该进程将被中止。大多数程序不会产生被捕获的 IEEE 浮点异常,这是因为,启用捕获的目的常常是在异常产生时中止程序,中止的方式是调用信号处理程序以打印消息并终止程序或者在未装信号处理程序的情况下借助于系统的缺省行为。但是,正如Chapter 4, 异常和异常处理中所述,可以安排信号处理程序为捕获指令提供结果并继续执行。请注意,如果捕获到许多浮点异常并用这种方式进行处理,则性能可能严重下降。
即使禁用捕获或者指令不引发已启用陷阱的异常,大多数 SPARC 浮点单元也会在某些情况下自陷,至少在涉及无穷大、NaN 操作数或 IEEE 浮点异常的情况下是这样。当硬件不支持类似的特殊情况时,将出现该问题。相反,它将生成 unfinished_FPop 陷阱并使内核模拟软件完成指令。不同的 SPARC FPU 对导致 unfinished_FPop 陷阱的条件会有不同的反应。例如,大多数早期的 SPARC FPU 会捕获所有的 IEEE 浮点异常而无论捕获是否被启用;在某浮点异常的陷阱被启用并且硬件无法确定某指令是否会导致该异常的情况下,UltraSPARC FPU 将进行捕获。但任何最新的 SPARC 处理器将在硬件中处理所有异常情况,并且从不产生 unfinished_FPop 陷阱。
由于大多数 unfinished_FPop 陷阱都与浮点异常一起出现,因此程序可通过利用异常处理功能(即,测试异常标志、捕获和提交结果或者中止异常)来避免导致过量陷阱。当然,必须认真平衡以下两个方面带来的成本:处理异常;允许异常引发 unfinished_FPop 陷阱。
在某些 SPARC 浮点单元产生 unfinished_FPop 陷阱的大多数常见情况下,都会生成次正规数。当浮点运算生成次正规操作数或者必须生成一个非零的次正规结果(即,导致渐进下溢的结果)时,许多较旧的 SPARC 浮点单元将自陷。因为下溢较少见但是又很难通过编程来回避,而且因为下溢的中间结果的准确性对最终计算结果的整体准确性影响很小,所以 SPARC 结构包括非标准算法模式,该模式为用户提供一种方法,用来避免出现与涉及次正规数的 unfinished_FPop 陷阱相关的性能的下降。
SPARC 结构不能准确定义非标准算法模式。它只是声明当该模式处于启用状态时,支持它的处理器会生成不符合 IEEE 754 标准的结果。但是,所有支持该模式的现有 SPARC 实现都使用它来禁用渐进下溢,并将所有的次正规操作数和结果替换为零。
并非所有的 SPARC 实现都提供非标准模式。不支持此模式的 SPARC 实现将忽略它,因此,数值和异常结果在非标准模式中是相同的。渐进下溢在这些处理器上并不造成性能损失。
为了确定渐进下溢是否影响程序的性能,应当首先确定下溢是否确实发生,然后检查该程序占用了多少系统时间。为了确定是否出现下溢,可以使用数学库函数 ieee_retrospective() 来查看在程序退出时是否引发了下溢异常标志。在缺省情况下,Fortran 程序调用 ieee_retrospective()。C 和 C++ 程序在退出之前需要显式调用 ieee_retrospective()。如果出现了任何下溢,ieee_retrospective() 会打印一条类似如下的消息:
Note: IEEE floating-point exception flags raised: Inexact; Underflow; See the Numerical Computation Guide, ieee_flags(3M)
如果该程序遇到下溢,您可能希望通过用 time 命令来对该程序的执行进行计时,从而确定该程序占用多少系统时间:
demo% /bin/time myprog > myprog.output real 305.3 user 32.4 sys 271.9
如果系统时间(上面输出的第三个数字)超长,则说明原因可能在于下溢数量太多。如果是这样的话,并且如果该程序不依赖渐进下溢的准确性,则可以启用非标准模式以获得更佳性能。
可通过两种方法来完成此操作:第一,可以用 –fns 标志(这暗示着它是 –fast 和 –fnonstd 宏的一部分)进行编译,以便在程序启动时启用非标准模式。第二,增值的数学库 libsunmath 提供两个分别用来启用和禁用非标准模式的函数:调用 nonstandard_arithmetic() 会启用非标准模式(如果它受支持的话),而调用 standard_arithmetic() 会恢复 IEEE 行为。 调用这些函数的 C 和 Fortran 语法如下所示:
|
在实现非标准模式的 SPARC 浮点单元上,启用该模式会导致硬件将次正规操作数视为零并将次正规结果刷新为零。但是,用于模拟已捕获浮点指令的内核软件不实现非标准模式,其部分原因在于该模式的影响无法定义且依赖于实现,而且,与在软件中模拟浮点运算所带来的成本相比,处理渐进下溢所带来的附加成本可忽略不计。
如果将受到非标准模式影响的浮点运算被中断(例如,当出现上下文切换或者另一个浮点指令引发陷阱时,已发出的运算将无法完成),它将由内核软件使用标准 IEEE 算法进行模拟。因此,在异常情况下,在非标准模式下运行的程序可能根据系统负荷生成稍有不同的结果。在实际中未发现这样的行为。它将只影响那些对以下情况非常敏感的程序:用渐进下溢还是用突然下溢执行极为罕见的特定运算。
用编译器分发的 fpversion 实用程序标识已安装的 CPU 并估计处理器和系统总线的时钟速度。fpversion 通过解释由 CPU 和 FPU 存储的标识信息来确定 CPU 和 FPU 的类型。 它对执行那些运行时间可以被预计的简单指令的循环进行计时,从而估计时钟速度。对该循环执行多次可增加计时测量的准确性。因此,fpversion 不是瞬间完成的,它可能需要运行几秒钟。
fpversion 还报告要用于主机系统的最佳 –xtarget 代码生成选项。
在 T4-2 服务器上,fpversion 显示类似如下的信息。这些信息可能因计时或机器配置的不同而异。
demo% fpversion A SPARC-based CPU is available. Kernel says CPU's clock rate is 1500.0 MHz. Kernel says main memory's clock rate is 150.0 MHz. Sun-4 floating-point controller version 0 found. An UltraSPARC chip is available. Use "-xtarget=T4 -xcache=16/32/4/8:128/32/8/8:4096/64/16/64" code-generation option. Hostid = hardware_host_id
有关更多信息,请参见 fpversion(1) 手册页。