为什么gcc数学库效率如此低下?

30

当我将一些Fortran代码转换成C时,令我惊讶的是:使用ifort(英特尔Fortran编译器)编译的Fortran程序和使用gcc编译的C程序之间大部分执行时间差异来自三角函数(sin, cos)的计算。这令我感到惊讶,因为我曾经相信这个答案所解释的,即正弦和余弦等函数是在微处理器内部的微码中实现的。

为了更明确地发现问题,我制作了一个小型Fortran测试程序。

program ftest
  implicit none
  real(8) :: x
  integer :: i
  x = 0d0
  do i = 1, 10000000
    x = cos (2d0 * x)
  end do
  write (*,*) x
end program ftest

intel Q6600 处理器和 3.6.9-1-ARCH x86_64 Linux 操作系统下,我使用的是 ifort version 12.1.0

$ ifort -o ftest ftest.f90 
$ time ./ftest
  -0.211417093282753     

real    0m0.280s
user    0m0.273s
sys     0m0.003s

而在 gcc 版本 4.7.2 中我得到了

$ gfortran -o ftest ftest.f90 
$ time ./ftest
  0.16184945593939115     

real    0m2.148s
user    0m2.090s
sys     0m0.003s

这几乎是一个10倍的差异!我仍然可以相信gcc对cos的实现类似于intel实现中可能做的方式,即它是微处理器实现的包装器吗?如果是这样,瓶颈在哪里?

编辑

根据评论,启用优化应该会提高性能。 我的意见是优化不会影响库函数...这并不意味着我不会在非平凡程序中使用它们。 但是,这里有两个额外的基准测试(现在在我的家用电脑上intel core2

$ gfortran -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m2.993s
user    0m2.986s
sys     0m0.000s

$ gfortran -Ofast -march=native -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m2.967s
user    0m2.960s
sys     0m0.003s
你(评论者)具体指的是哪些优化?在这个特定的例子中,每次迭代都依赖于上一次的结果,编译器如何利用多核处理器进行优化?
修改2:
Daniel Fisher和Ilmari Karonen的基准测试让我想到问题可能与我在计算机上使用的特定版本(Arch x86_64 Linux的gcc 4.7.2)有关。因此,我在debian x86_64 Linux上的intel core i7电脑上重复了测试,使用了gcc version 4.4.5ifort version 12.1.0
$ gfortran -O3 -o ftest ftest.f90
$ time ./ftest
  0.16184945593939115     

real    0m0.272s
user    0m0.268s
sys     0m0.004s

$ ifort -O3 -o ftest ftest.f90
$ time ./ftest
  -0.211417093282753     

real    0m0.178s
user    0m0.176s
sys     0m0.004s

对我来说,这个性能差异是可以接受的,不会让我产生疑问。看来我需要在Arch Linux论坛上询问这个问题。

然而,整个故事的解释仍然非常受欢迎。


3
为什么计算出来的值不同? - chill
2
离x8更近了,你有一台有8个核心的机器吗?自动向量化也应该发挥作用,但你禁用了它。在不启用优化器的情况下进行基准测试是浪费时间。 - Hans Passant
4
使用优化编译或离开。 - ams
2
没有优化,数值将会从内存中存取,而有了优化,它们都在寄存器中。即使有CPU缓存,这也可能解释了差异...如果你想深入了解,可以尝试查看编译器的汇编输出。 - hyde
3
@Digikata,ifort不会执行任何类型的循环展开。它会非常字面地翻译循环,并调用其自己优化过的cos例程,该例程以某种方式比fcos更快地计算余弦。初步查看,反汇编的cos例程似乎使用表格化参数缩减。GCC调用系统范围内的数学库,实现了类似的功能,但对于英特尔处理器来说没有libimf进行了优化。 - Hristo Iliev
显示剩余19条评论
1个回答

18

大部分原因是由于数学库的差异。以下是需要考虑的一些要点:

  • 是的,带有x87单元的x86处理器具有fsin和fcos指令。然而,它们是通过微码实现的,并没有什么特别的理由可以说明它们一定比纯软件实现更快。
  • GCC没有自己的数学库,而是使用系统提供的库。在Linux上,通常由glibc提供。
  • 32位x86 glibc使用fsin/fcos。
  • x86_64 glibc使用使用SSE2单元的软件实现。很长一段时间以来,这比仅使用x87指令的32位glibc版本要慢得多。但是,在最近进行了改进后,情况可能不像过去那么糟糕,具体取决于您使用的glibc版本。
  • 英特尔编译器套件拥有非常快的数学库(libimf)。此外,它还包括矢量化的超越函数,可以经常加速使用这些函数的循环。

1
fsinfcos比C语言中的实现更快,但它们并不太精确,因为它们基于错误的π值(瑟克!)。 - fuz
@fuz:它并不是一个错误的值,但它只有80位的long double精度(64位有效数字,接下来的2个数字恰好为0,因此是66位有效数字)。fsin范围缩减使用正确舍入为80位long double的Pi。但是,在+-Pi附近,这不够好,因为它会导致灾难性的抵消,但是要用软件做得更好需要扩展精度。(例如double double我猜) - Peter Cordes
还有相关的:在LLVM中调用fsincos指令比调用libc sin/cos函数慢吗?,显示了软件数学库比fsincos更快,而不是更慢。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接