我不知道为什么ICC选择将堆栈对齐2个缓存行:
and rsp, -128 #2.1
sub rsp, 128 #2.1
那很有意思。 L2 高速缓存有一个相邻行预取器,喜欢将行对(在 128 字节对齐组中)拉入 L2。但 main 的栈帧通常不会被大量使用。也许在某些程序中重要的变量被分配在那里。 (这也解释了设置rbp
,保存旧的 RSP,以便它可以在 AND 运算后返回。 gcc 在函数中使用 RBP 制作堆栈帧,并在其中对齐堆栈。)
剩下的就是因为main()
很特殊,ICC使-ffast-math
默认情况下。 (这是英特尔的“肮脏”小秘密之一,让它自动矢量化更多开箱即用的浮点代码。)
这包括将代码添加到顶部main
设置 MXCSR(SSE 状态/控制寄存器)中的 DAZ/FTZ 位。有关这些位的更多信息,请参阅 Intel 的 x86 手册,但它们实际上并不复杂:
有关的:SSE“非正规数为零”选项 https://stackoverflow.com/questions/37886551/sse-denormals-are-zeros-option
(ISO C++ 禁止程序回调main()
,因此编译器可以将运行一次的内容放入main
本身而不是 CRT 启动文件中。 gcc/clang 与-ffast-math
指定用于设置 MXCSR 的 CRT 启动文件中的链接。但是,当使用 gcc/clang 编译时,它只会影响允许优化的代码生成。即,将 FP add/mul 视为关联,而不同的临时值意味着它实际上不是关联。这与设置DAZ/FTZ完全无关)。
非正规在这里被用作次正规的同义词:具有最小指数和有效数字的 FP 值,其中隐式前导位为 0 而不是 1。即,幅度小于的值FLT_MIN or DBL_MIN https://stackoverflow.com/questions/39746861/is-dbl-min-the-smallest-positive-double,最小的可表示的归一化浮点数/双精度。
https://en.wikipedia.org/wiki/Denormal_number https://en.wikipedia.org/wiki/Denormal_number.
产生次正常结果的指令可以是much较慢:为了优化延迟,某些硬件中的快速路径假定标准化结果,如果结果无法标准化,则采用微代码辅助。使用perf stat -e fp_assist.any
来计算此类事件。
来自 Bruce Dawson 的优秀 FP 文章系列:这不正常——奇数浮点数的表现 https://randomascii.wordpress.com/2012/05/20/thats-not-normalthe-performance-of-odd-floats/. Also:
- 为什么将 0.1f 更改为 0 会使性能降低 10 倍? https://stackoverflow.com/questions/9314534/why-does-changing-0-1f-to-0-slow-down-performance-by-10x
- 避免 C++ 中的非正规值 https://stackoverflow.com/questions/2487653/avoiding-denormal-values-in-c/2487733#2487733
Agner Fog 做了一些测试(参见他的微架构pdf https://agner.org/optimize/),以及 Haswell/Broadwell 的报告:
下溢和次正常
当浮点运算接近时,会出现次正规数
下溢。在某些情况下,处理次正规数的成本非常高
因为次正常结果是由微代码处理的情况
例外情况。
Haswell 和 Broadwell 的惩罚大约为 124 个时钟
在所有情况下循环,其中对正常数的运算给出
低于正常的结果。乘法也有类似的惩罚
介于正常数和次正常数之间,无论是否
结果正常或低于正常。添加正常值不会受到任何惩罚
和一个次正规数,无论结果如何。没有处罚
对于上溢、下溢、无穷大或非数字结果。
如果“清零”,则可以避免对次正规数的惩罚
模式和“非正规数为零”模式均在 MXCSR 中设置
登记。
因此,在某些情况下,现代英特尔 CPU 即使在次正常情况下也能避免处罚,但是