AVX指令集中的32种浮点比较关系详解

  在传统印象中,数字的比较关系只有6种。但在AVX指令集中,Intel一下给出了32种浮点比较谓词,详见下图--

(Intel手册:Table 3-9. Comparison Predicate for VCMPPD and VCMPPS Instructions)

  为什么会有这么多种比较谓词呢?我为此困惑困惑了很久。
  直到最近翻阅了不少资料后,才终于将它们弄懂了。

一、浮点数据类型

  Intel使用的是IEEE 754规范的浮点数据类型。对于浮点数据类型来说,除了可以存储数字、无穷之外,还可以存储 NaN(not a number。非数)。

  NaN(非数)分为两大类--
1.QNaN:quiet NaN,静默非数。当禁止异常时,若浮点运算无效(例如 “零除以零”、“对负数开方”等),就返回QNaN表示运算无效。
2.SNaN:signaling NaN,信号非数。当浮点运算遇到SNaN时,会立即抛出异常。例如可以用来检查变量是否被初始化(定义变量时设为SNaN,如果用户没有将它初始化就调用浮点运算,会抛出异常)。

  在Intel手册上有张浮点数编码表,位于“Volume 1: Basic Architecture ”\“Chapter 4 Data Types”\“4.2 Numeric Data Types”\“4.2.2 Floating-Point Data Types”--

(Intel手册:Table 4-3. Floating-Point Number and NaN Encodings)

  从上表中可以得知--
1.NaN的阶码为全1,符号位被忽略。通过尾数来判断是SNaN或QNaN。
2.SNaN的尾数的最高为0,其他位非0。
3.QNaN的尾数的最高为1,其他位非0。
4.还存在一个特殊的QNaN--QNaN Floating-Point Indefinite:表示“不确定浮点值”的静默非数。可参考周明德《64位微处理器应用编程》“5.4.4数的编码”中“3、不确定值”。

  关于NaN的运算细节,可参考Intel手册中“4.8.3.4 NaNs”至“4.8.3.7 QNaN Floating-Point Indefinite”的内容。


二、两种特殊的比较关系--“无序”、“有序”

  传统的数字的比较关系是这6种--
1.等于(Equal)。谓词缩写为“EQ”。C语言运算符为“==”。
2.小于(Less than)。谓词缩写为“LT”。C语言运算符为“<”。
3.小于等于(Less than or equal)。谓词缩写为“LE”。C语言运算符为“”。
5.大于等于(Greater than or equal)。谓词缩写为“GE”。C语言运算符为“>=”。
6.不等于(Not equal)。谓词缩写为“NEQ”。C语言运算符为“!=”。

  一般来说,上面的6种比较关系只对数字(包括无穷大)有效。即两边的操作数都必须是数字,任何一个都不能为NaN。
  若任一操作数是NaN(非数),那么将无法进行比较,前5种比较关系都返回False。包括“NaN==NaN”的返回值都是False。
  而“不等于”就有点微妙,因为它是“等于”的相反值。所以当任一操作数是NaN时,“不等于”也返回True。因此,将它称为“不等于或无序”将会更好。

  “无序”是专门为NaN而设计的比较关系,详细定义如下--
无序(Unordered)。当至少一个操作数是NaN时,返回True。若操作数都不是NaN时,返回False。谓词缩写为“UNORD”。一般借用符号“?”表示“无序”关系。

  既然有了“无序”,那么可以定义一个跟它效果相反的运算符“有序”--
有序(Ordered)。当操作数都不是Nan时,返回True。若至少一个操作数是NaN时,返回False。谓词缩写为“ORD”。一般借用符号“!?”表示“有序”关系。

  可这样理解--
有序:能比较大小。
无序:不能比较大小。


三、SIMD(SSE/AVX)中的NaN与异常

  在使用AVX指令集时,依然是靠MXCSR寄存器来控制浮点运算的。在Intel手册“10.2.3 MXCSR Control and Status Register”中有该寄存器的详细定义--
(Intel手册:Figure 10-3. MXCSR Control/Status Register)

  当CPU发现浮点运算无效时--
1. 首先将MXCSR寄存器的最低位(IE:Invalid Operation Flag)设为1。
2. 随后CPU检查MXCSR寄存器的第7位(IM:Invalid Operation Mask),若IM为1(masked,屏蔽),就返回QNaN(或特殊值);若IM为0(unmasked,非屏蔽),就触发#IA异常。
注:#IA是无效算术操作异常,详见Intel手册“8.5.1.2 Invalid Arithmetic Operand Exception (#IA)”。

  一般来说,等于、小于等比较操作遇到NaN时会引发上述流程--
1.将IE设为1。
2.若IM为1,不会触发异常,返回比较结果;若IM为0,会触发异常。

  而对于“无序”比较来说,因为它本来就是用来检查NaN,不希望触发异常。所以CPU对这一类操作做了特殊处理,不会执行上述操作。

  即浮点比较会因是否抛出异常而分为两类--
1.signaling(发信号):遇到NaN时,设置IE,根据IM决定是返回比较结果,还是抛出异常。
2.non-signaling(不发信号): 遇到NaN时,总是返回比较结果,从不抛出异常。



四、32种浮点比较关系详解

  学习了上面那些知识点后,现在可以读懂AVX指令集中的32种浮点比较关系了。

Predicate:比较谓词。
imm8 Value:8位立即数。
Description:描述。
Result: A Is 1st Operand, B Is 2nd Operand:返回值:A是第一个操作数,B是第二个操作数。
A > B:大于。
A < B:小于。
A = B:等于。
Unordered:无序。
Signals #IA on QNAN:当遇到QNaN时是否发信号#IA。
注:较复杂的比较关系可以由“大于”、“小于”、“等于”、“无序”这四种基本关系所组成。例如“小于等于”由“小于”、“等于”组成。


  我将该表格翻译了一下,并增加了符号形式描述--

谓词:比较谓词。
imm8:8位立即数。
符:符号形式描述。
描述:文字形式描述。
符2:另一种符号形式描述。
描述2:另一种文字形式描述。
A > B:大于。
A < B:小于。
A = B:等于。
无序:无序。
#IA:Signals #IA on QNAN。

  观察这张表格,会发现谓词的命名是有规则的,它由三部分组成,格式大致为“①_②③”,含义如下--
①.下划线前的是第一部分,它是谓词缩写,用于描述功能。例如“EQ_OQ”中的“EQ”代表“等于”。
②.在下划线与最后字母之间的是第二部分,只能是O、U或忽略。O代表有序,U代表无序。
③.最后的那个字母是第三部分,只能是Q或S,分别代表non-signaling和signaling。例如“EQ_OQ”中的“Q”代表“non-signaling”。该字母其实代表的是#IA这一列,为“Q”时“#IA”列为“No”,为“S”时“#IA”列为“Yes”。

  AVX指令集中的VCMPPS等比较指令是继承自SSE指令集中的CMPPS等指令的,它们靠一个8位立即数(imm8)来区分比较谓词。
  SSE指令集只定义了前8种比较谓词,所以imm8只有最低3位有效。上表“谓词”后面括号内的就是SSE时代的谓词名称。
  AVX指令集将其扩展到32种比较谓词,所以imm8只有最低5位有效。

  Intel在设计AVX指令集的比较谓词时,先根据无序、有序和FALSE、TRUE,将比较谓词扩充到16种。然后再反转non-signaling/signaling,又定义了16种比较谓词。合计32种比较谓词。



参考文献--
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. December 2011. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《IEEE Standard for Floating-Point Arithmetic (IEEE 754-2008)》。 Institute of Electrical and Electronics Engineers (IEEE)。2008年8月。
《GB/T 17966-2000 微处理器系统的二进制浮点运算》。国家质量技术监督局。2000年1月。
《Pentium Ⅱ/Ⅲ体系结构及扩展技术》。刘清森、马鸣锦、吴灏等著。国防工业出版社,2000年7月。
《64位微处理器应用编程》。周明德著。清华大学出版社,2005 年9月。
NaN:http://en.wikipedia.org/wiki/NaN

标签:
如需更全面地了解编译器优化,请参阅优化注意事项