Fortran计算4字节实数的平方根与整数加法一样快吗?

4
剧透:测试程序没有对结果进行任何操作,因此优化编译器删除了循环的内容,因此每次运行循环都需要花费相同的时间...无论如何,我会保留问题和答案,以防有人(包括我自己?)再次犯同样的错误。

原始帖子: 我想测试计算平方根与简单加法相比慢多少,于是写了下面这个小程序。结果表明,在这种情况下,它需要大约相同的时间,即0.3秒。这是怎么回事?

program sqtest
implicit none
real r, s
integer i,j,n, sq, t

sq=11
n=100000000
r=1.11

if (sq==1) then
 do i = 1,n
  s = sqrt(float(i)*r)
 enddo
 write(*,*) "squareroot"
else
 do j = 1,n
  t = j+4
 enddo
 write(*,*) "plus"
endif


end program

sq=1赋值给变量,以使用平方根。平方根循环同时进行乘法操作并将int转换为float


是的,这种情况可能存在。如果我写入文件,会花费更长的时间,但是输入/输出可能是限制因素。我想我需要进行更加深思熟虑的测试。我使用gfortran编译器,我的笔记本电脑和操作系统都是64位,并且有8GB的内存。不过,对于你的问题,我不知道确切的答案。 - Jonatan Öström
当你进行这样的测试时,应该检查汇编代码(例如使用objdump -d)以确保编译器生成了你期望的代码。在这种情况下,我相信编译器删除了do循环,因为结果没有被使用。你最终应该执行s = s+sqrt(float(i)*r)并在循环后打印值,以避免代码被删除。此外,从int到float的转换是昂贵的,你应该可能要做i_float = 0. ; do i=1,n ; i_float = i_float + 1. ; ...来避免转换。 - Anthony Scemama
3个回答

3

在进行此类测试时,需要考虑许多因素。首先必须清楚定义比较的内容。对于这种简单的测试,您还应该停用优化。大多数主要编译器都接受选项-O0来停用优化。否则,编译器将发现您没有对计算出的值进行任何操作,并且甚至不执行循环,因为它毫无意义。

简而言之,我稍微修改了您的程序,以达到以下效果:

program sqtest
implicit none
real r0, r1, r2, s
integer i,n
real :: start, finish


    n=10**9
    call random_number(r0)
    call random_number(r1)
    call random_number(r2)


    call cpu_time(start)
    do i = 1,n
        s = sqrt(r0)
    enddo
    call cpu_time(finish)
    print '("SQRT:      Time = ",f6.3," seconds.")',finish-start

    call cpu_time(start)
    do i = 1,n
        s = r1+r2
    enddo
    call cpu_time(finish)
    print '("Addtition: Time = ",f6.3," seconds.")',finish-start

end program

在我的系统上,它给了我以下结果:
ifort 13, n = 10^8
SQRT:      Time =  0.378 seconds
Addtition: Time =  0.202 seconds

ifort 13, n = 10^9
SQRT:      Time =  3.460 seconds
Addtition: Time =  1.857 seconds

gfortran (GCC) 4.9, n = 10^8
SQRT:      Time =  0.385 seconds
Addtition: Time =  0.191 seconds

gfortran (GCC) 4.9, n = 10^9
SQRT:      Time =  3.529 seconds
Addtition: Time =  1.733 seconds

pgf90 14, n = 10^8
SQRT:      Time =  0.380 seconds
Addtition: Time =  0.058 seconds

pgf90 14, n = 10^9
SQRT:      Time =  3.438 seconds
Addtition: Time =  0.520 sec

您会注意到我在代码中调用了CPU时间。为了使数字具有意义,您应该多次运行每个案例并计算时间平均值或选择最小值。最小值接近于您的系统在最佳条件下可以实现的结果。
您还将看到结果取决于编译器。pgf90在加法方面显然给出了更好的结果。我从平方根中删除了float(i)*。gfortran和pgf90在这方面表现非常快(n = 10^9时约为2.6秒),而ifort的表现非常慢(n = 10^9时约为7.3秒)。这意味着某种方式gfortran和pgf90在那里选择了不同的路径(更快的操作),也许它们进行了一些优化,尽管我已将其禁用?

我觉得这非常有帮助和有趣!非常感谢!我的最初问题是关于代码设计的,以及计算平方根的惩罚是什么。看起来确实非常小。虽然不是零,但我的测试让我感到困惑。我已经看到一些数字,表明这些操作比加法或乘法昂贵了几个数量级。 - Jonatan Öström
不客气!说操作比加法或乘法昂贵许多的数字是非常正确的。现代架构的区别在于流水线。在这里,我们充分利用了流水线。由于所有迭代可以并行运行,差异在于循环吞吐量的倒数。例如,如果add的值为1,而sqrt的值为2,即使sqrt需要比add多100倍的周期,sqrt只会慢2倍,因为我们有大量的迭代。 - innoSPG
我认为相对性能很可能也与处理器有关。例如,英特尔Fortran编译器(ifort)在英特尔创建的芯片上表现会更好。 - jvriesem

2
您可以在此文档中找到硬件平方根的成本:http://www.agner.org/optimize/instruction_tables.pdf
平方根可以通过不同的方式计算。通常,这是一个迭代过程,仅涉及加法和乘法运算。 通常情况下,平方根被计算为sqrt(x) = x * (1/sqrt(x)),因为(1/sqrt(x))的计算速度比sqrt(x)更快。
如果您使用Haswell CPU,则单精度SQRTSS指令的延迟为11个周期,双精度(SQRTSD)的延迟为16个周期。 在单精度中,需要较少的迭代次数以达到所需的精度,而在双精度中则需要更多。在同一CPU上,有一个近似版本的平方根(RSQRTSS),延迟为1个周期,因此如果您要求激进的优化,编译器可能会选择生成这个指令。
如果您需要多个独立的平方根,例如在您的示例中,代码可以由编译器自动矢量化。存在矢量化变体VSQRTPS,其倒数吞吐量为14。在这种情况下,您将获得大约14/8 = 1.75个周期的平均平方根。
参考资料:

不错的东西,谢谢!您的意思是它针对单个核心进行SIMD向量化处理吗?因为据我所知,没有自动跨核心进行多线程处理。 - Jonatan Öström

1
也许你的编译器正在优化代码。您可以通过测量不同n的顺序(例如1e6、1e7、1e8等)来测试这一点,并查看时间如何缩放。顺便问一下,您的机器/编译器允许整数的范围是多少?

这是一个答案,但需要稍作详细说明。 - agentp
是的,可能会出现这种情况。如果我写入文件,那么需要更长的时间,但是输入/输出可能会受到限制。我想我必须进行更加深思熟虑的测试。我使用gfortran,我的笔记本电脑和操作系统都是64位,有8GB的内存。不过,我不知道你问题的确切答案。 - Jonatan Öström
2
任何一款半过得去的优化编译器都会看到那个程序,意识到它没有做任何工作,并删除除打印语句之外的所有代码。我认为试图计时单个操作是浪费精力的,特别是如果你不熟悉如何编写实际测试你所寻找的内容的基准测试。 - Steve Lionel

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