c++ - 比较 int = floor(sqrt(...)) 的 Fortran 和 C++ 汇编器
问题描述
我在 Fortran 和 C++ 中分别实现了一个函数:
#include <math.h>
void dbl_sqrt_c(double *x, double *y){
*y = sqrt(*x - 1.0);
return;
}
pure subroutine my_dbl_sqrt(x,y) bind(c, name="dbl_sqrt_fort")
USE, INTRINSIC :: ISO_C_BINDING
implicit none
real(kind=c_double), intent(in) :: x
real(kind=c_double), intent(out) :: y
y = sqrt(x - 1d0)
end subroutine my_dbl_sqrt
我在编译器资源管理器中比较了它们:
Fortran:https
://godbolt.org/z/froz4rx97
C++:https ://godbolt.org/z/45aex99Yz
而我阅读汇编程序的方式,它们基本上做同样的事情,但 C++ 会检查 sqrt 的参数是否为负,而 Fortran 不会。我使用谷歌基准比较了它们的性能,但它们非常匹配:
--------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------
bm_dbl_c/8 2.07 ns 2.07 ns 335965892
bm_dbl_fort/8 2.06 ns 2.06 ns 338643106
这是有趣的部分。如果我把它变成基于整数的函数:
void int_sqrt_c(int *x, int *y){
*y = floor(sqrt(*x - 1.0));
return;
}
和
pure subroutine my_int_root(x,y) bind(c, name="int_sqrt_fort")
USE, INTRINSIC :: ISO_C_BINDING
implicit none
integer(kind=c_int), intent(in) :: x
integer(kind=c_int), intent(out) :: y
y = floor(sqrt(x - 1d0))
end subroutine my_int_root
然后这是他们开始分歧的地方:
--------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------
bm_int_c/8 3.05 ns 3.05 ns 229239198
bm_int_fort/8 2.13 ns 2.13 ns 328933185
Fortran 代码似乎并没有因为这种变化而明显变慢,但 C++ 代码却减慢了 50%。这似乎相当大。这些是程序集:
Fortran:https
://godbolt.org/z/axqqrc5E1
C++:https ://godbolt.org/z/h7K75oKbn
Fortran 程序集看起来很简单。它只是增加了和之间的转换double
,int
并没有太多其他的东西,但 C++ 似乎做得更多,我不完全理解。
为什么 C++ 汇编器要复杂得多?如何改进 C++ 代码以实现匹配的性能?
解决方案
TL;DR:你被糟糕的默认设置和与过时机器的兼容性所困扰:糟糕的默认设置是errno
用于浮点计算的 gcc 设置(尽管 C 语言不需要这样做),以及与没有任何设备的 x86 机器的兼容性比 SSE2 更好的 SSE 指令。如果您想要体面的代码生成,请添加-fno-math-errno -msse4
到编译器标志。
包含浮点硬件的现代处理器架构通常将平方根计算作为基本操作(指令)提供,如果平方根指令的操作数超出范围(负数),则会在浮点环境中设置错误标志。另一方面,旧的指令集架构可能没有浮点指令,或者没有硬件加速的平方根指令,因此 C 语言允许实现设置errno
超出范围的参数,但errno
它是线程本地的内存位置实际上阻止了任何健全的架构errno
直接从平方根指令设置。为了获得不错的性能,gcc 通过调用硬件指令 ( ) 内联平方根计算sqrtsd
,但要设置errno
,它单独检查参数的符号,只有在参数为负的情况下才调用库函数,所以库函数可以设置errno
。是的,这很疯狂,但这反过来又是课程的标准。-fno-math-errno
您可以通过设置编译器标志来避免这种没人需要或想要的脑损伤。
相对较新的 x86-64 处理器具有比 AMD 首次开发的原始 x86-64 中更多的指令(其中仅包括 SSE2 向量/浮点指令)。添加的指令中有浮点/整数转换指令,允许受控舍入/截断,因此不必在软件中实现。您可以通过指定支持这些指令的目标来让 gcc 使用这些新指令,例如使用-msse4
编译器标志。请注意,如果生成的程序在不支持这些指令的目标上运行,这将导致生成的程序出错,因此生成的程序的可移植性会降低(尽管它不会明显降低源代码的可移植性)。
推荐阅读
- google-cloud-platform - GCP 从现有虚拟机创建实例模板
- java - 如何确定活动在哪个日历上?
- python - Python 数据表:sum、groupby、column < 0
- html - 如何为列中的图像设置中心对齐并使文本出现在其下方?
- powerbi - Power BI 增长率计算 YoY
- linux - 验证文件仅在 2 次提交之间最后被修改
- jasperserver - 7.2.0版本的jasper服务器如何查找jasper报告编译器版本
- scala - 如何优雅地从数据库初始化 Scala 服务?
- r - Knitr table 和 formattable 函数在 Rstudio & Shiny 中不起作用
- javascript - JSON:使用 Pexels API 密钥