c++ - 比较 Fortran 和 C++ 汇编程序的 int = floor(sqrt(...))

标签 c++ performance assembly x86-64

我在 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 则没有。我使用 googles 基准比较了它们的性能,但它们非常匹配:
--------------------------------------------------------
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:你被糟糕的默认设置和与过时机器的兼容性所困扰:糟糕的默认设置是 gcc 设置 errno用于浮点计算(尽管 C 语言不需要),以及与没有比 SSE2 更好的 SSE 指令的 x86 机器的兼容性。如果你想要体面的代码生成,添加 -fno-math-errno -msse4compiler flags .
包含浮点硬件的现代处理器体系结构通常提供平方根计算作为原始操作(指令),如果平方根指令的操作数超出范围(负),则在浮点环境中设置错误标志。另一方面,旧的指令集架构可能没有浮点指令,或者没有硬件加速平方根指令,所以 C 语言允许实现设置 errno。而是使用超出范围的参数,但是 errno作为线程本地内存位置实际上可以防止任何理智的架构设置 errno直接来自平方根指令。为了获得不错的性能,gcc 通过调用硬件指令 ( sqrtsd ) 来内联平方根计算,但要设置 errno ,它单独检查参数的符号,只有在参数为负的情况下才调用库函数,所以库函数可以设置errno .是的,这很疯狂,但这反过来又是类(class)的标准。您可以通过设置 -fno-math-errno 来避免这种没人需要或想要的脑损伤。在编译器标志中。
合理地最近的 x86-64 处理器比最初由 AMD 开发的原始 x86-64(仅包括 SSE2 vector/浮点指令)中的指令更多。添加的指令包括浮点数/整数转换指令,允许受控舍入/截断,因此不必在软件中实现。您可以通过指定支持这些指令的目标来让 gcc 使用这些新指令,例如使用 -msse4编译器标志。请注意,如果生成的程序运行在不支持这些指令的目标上,这将导致生成的程序出错,因此生成的程序的可移植性会降低(尽管它不会明显降低源代码的可移植性)。

关于c++ - 比较 Fortran 和 C++ 汇编程序的 int = floor(sqrt(...)),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67046739/

相关文章:

c++ - 在 while 循环中将线程置于另一个线程中

django - 调整 postgresql(使用 django 快速读取)

assembly - BIOS 如何区分 INT 指令中的中断 (08h-12h) 与 CPU 内部的实际异常?

c - 从汇编中确定 C 函数的参数类型

assembly - MIPS assembly 标签

c++ - 比较有符号和无符号字符

java - 如何在Android中添加2个org.opencv.core.Point对象?

c++将格式化的字符串作为参数传递给函数

C++ 为什么原始 double 组中的赋值似乎比 double 变量赋值快得多?

performance - 使用带有 Hibernate 的 Spring Transactions,如何在同一事务中获得 2000 多个插入而不减慢速度?