【问题标题】:A simple test case between clang++/g++/gfortranclang++/g++/gfortran之间的一个简单测试用例
【发布时间】:2024-04-13 18:15:01
【问题描述】:

我在scicomp 上遇到了这个问题,其中涉及计算总和。在那里,您可以看到 c++ 和类似的 fortran 实现。有趣的是,我看到 fortran 版本快了大约 32%。

我想,我不确定他们的结果,并试图恢复这种情况。这是我运行的(非常轻微的)不同的代码:

c++

#include <iostream>
#include <complex>
#include <cmath>
#include <iomanip>

int main ()
{
    const double alpha = 1;
    std::cout.precision(16);

    std::complex<double> sum = 0;
    const std::complex<double> a = std::complex<double>(1,1)/std::sqrt(2.);
    for (unsigned int k=1; k<10000000; ++k)
    {
        sum += std::pow(a, k)*std::pow(k, -alpha);

        if (k % 1000000 == 0)
            std::cout << k << ' ' << sum << std::endl;
    }

    return 0;
}

fortran

implicit none
integer, parameter :: dp = kind(0.d0)
complex(dp), parameter :: i_ = (0, 1)

real(dp) :: alpha = 1
complex(dp) :: s = 0
integer :: k
do k = 1, 10000000
    s = s + ((i_+1)/sqrt(2._dp))**k * k**(-alpha)
    if (modulo(k, 1000000) == 0) print *, k, s
end do
end

我在带有-O3 标志的Ubuntu 12.04 LTS 机器上使用gcc 4.6.3clang 3.0 编译上述代码。这是我的时间安排:

time ./a.out

gfortran

real    0m1.538s
user    0m1.536s
sys     0m0.000s

g++

real    0m2.225s
user    0m2.228s
sys     0m0.000s

叮当声

real    0m1.250s
user    0m1.244s
sys     0m0.004s

有趣的是,当使用gcc 时,我还可以看到fortran 代码比c++ 快大约相同的32%。但是,使用clang,我可以看到c++ 代码实际上运行速度快了大约19%。以下是我的问题:

  1. 为什么 g++ 生成的代码比 gfortran 慢?由于它们来自同一个编译器系列,这是否意味着(这个)fortran 代码可以简单地翻译成更快的代码? fortran vs c++ 通常是这种情况吗?
  2. 为什么clang 在这里做得这么好? llvm 编译器是否有 fortran 前端?如果有,那生成的代码会更快吗?

更新:

使用-ffast-math -O3 选项会产生以下结果:

gfortran

real    0m1.515s
user    0m1.512s
sys     0m0.000s

g++

real    0m1.478s
user    0m1.476s
sys     0m0.000s

叮当声

real    0m1.253s
user    0m1.252s
sys     0m0.000s

Npw g++ 版本的运行速度与 gfortran 一样快,而 clang 仍然比两者都快。在上述选项中添加-fcx-fortran-rules 并不会显着改变结果

【问题讨论】:

  • 请提供用于编译的编译器选项。 gcc 的 -ffast-math 等选项可能会显着影响时间。
  • @NikolayViskov 我在所有编译器上明确使用的唯一标志是-O3
  • 在我的机器上:clang 0.62 (-ffast-math 0.60), g++4.6 1.23 (-ffast-math 0.78), g++4.7 1.19 (-ffast-math 0.76)
  • 你真的需要输出吗?对我来说,删除条件和 ostream 输出将 C++ 版本的速度提高了十倍,这表明速度很慢是在不进行计算的情况下进行的。即使没有输出,Clang 仍然比 G++ 快得多,除非我使用 -std=c++11 并且突然 Clang 变得非常慢。
  • @JonathanWakely:我的经验是,如果没有输出,gfortran 编译的代码实际上不会运行(恒定的 0.001 秒运行时间)。

标签: c++ gcc fortran llvm clang


【解决方案1】:

时间差异将与执行pow所需的时间有关,因为其他代码相对简单。您可以通过分析来检查这一点。那么问题是编译器如何计算幂函数?

我的时间安排:使用 gfortran -O3 的 Fortran 版本约为 1.20 秒,使用 g++ -O3 -ffast-math 编译的 C++ 版本约为 1.07 秒。请注意-ffast-mathgfortran 无关,因为pow 将从库中调用,但它对g++ 有很大的不同。

在我的例子中,对于 gfortran,它是函数 _gfortran_pow_c8_i4 被调用 (source code)。它们的实现是计算整数幂的常用方法。另一方面,g++ 是来自 libstdc++ 库的函数模板,但我不知道它是如何实现的。显然,它的编写/可优化性稍好一些。考虑到它是一个模板,我不知道该函数在多大程度上是动态编译的。值得一提的是,使用 ifort 编译的 Fortran 版本和使用 icc 编译的 C++ 版本(使用 -fast 优化标志)都给出了相同的时序,所以我猜它们使用相同的库函数。

如果我只是在 Fortran 中用复杂的算术编写一个幂函数(明确写出实部和虚部),它与使用 g++ 编译的 C++ 版本一样快(但随后 -ffast-math 会减慢它的速度,所以我卡住了只有-O3gfortran):

complex(8) function pow_c8_i4(a, k)
implicit none

integer, intent(in) :: k
complex(8), intent(in) :: a

real(8) :: Re_a, Im_a, Re_pow, Im_pow, tmp
integer :: i

Re_pow = 1.0_8
Im_pow = 0.0_8
Re_a = real(a)
Im_a = aimag(a)
i = k

do while (i.ne.0)
  if (iand(i,1).eq.1) then
    tmp = Re_pow
    Re_pow = Re_pow*Re_a-Im_pow*Im_a
    Im_pow = tmp   *Im_a+Im_pow*Re_a
  end if
  i = ishft(i,-1)
  tmp = Re_a
  Re_a = Re_a**2-Im_a**2
  Im_a = 2*tmp*Im_a
end do
pow_c8_i4 = cmplx(Re_pow,Im_pow,8)
end function

根据我的经验,在 Fortran 实现中使用显式实部和虚部会更快,尽管使用复杂类型当然非常方便。

最后说明:尽管这只是一个示例,但每次迭代调用幂函数的方式效率极低。相反,您当然应该在每次迭代时将 a 自身相乘。

【讨论】:

    【解决方案2】:

    我相信您的问题出在输出部分。众所周知,C++ 流 (std::cout) 通常效率很低。虽然不同的编译器可能会对此进行优化,但使用 C printf 函数而不是 std::cout 重写关键性能部分始终是一个好主意。

    【讨论】:

    • 我认为 10 次打印输出不会影响 ~1.0 秒范围内的性能。
    • 你可能是对的。我自己没有测试过,但这绝对是人们应该始终关注的想法。
    最近更新 更多