【问题标题】:Fortran computes square root of real*4 as fast as addition of ints?Fortran 计算 real*4 的平方根和计算整数的加法一样快?
【发布时间】:2023-03-30 10:27:02
【问题描述】:

剧透:测试程序没有对结果做任何事情,所以循环的内容被优化编译器删除了,因此每次运行都需要大约相同的时间......无论如何,如果有人(我?)犯了同样的错误(再次......),我会让问题和答案保留下来。

原帖: 我想测试计算平方根与简单加法相比要慢多少,并编写了下面的小程序。我得到的结果是它需要大约相同的时间,在这种情况下为 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 以使用平方根。平方根循环也进行了从intfloat 的乘法和转换。

【问题讨论】:

  • 是的,可能是这样。如果我写入文件需要更长的时间,但是 i/o 可能会受到限制。我想我必须通过测试做出更多的思考。我使用 gfortran,我的笔记本电脑和操作系统是 64 位的,如果是 ram,则为 8gb。我不知道你的问题的确切答案。
  • 当您进行此类测试时,您应该检查程序集(例如使用objdump -d)以确保编译器生成了您期望的代码。在这种情况下,我相信编译器删除了 do 循环,因为没有使用结果。您最终应该执行s = s+sqrt(float(i)*r) 并在循环后打印该值以避免删除代码。此外,从 int 到 float 的转换很昂贵,您可能应该使用i_float = 0. ; do i=1,n ; i_float = i_float + 1. ; ... 来避免转换。

标签: fortran square-root


【解决方案1】:

在进行此类测试时需要考虑很多事情。您必须首先明确定义要比较的内容。对于这样简单的测试,您还应该停用优化,大多数主要编译器都接受选项-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 以某种方式在那里选择了不同的路径(更快的操作),即使我禁用了它,他们也可能做了一些优化?

【讨论】:

  • 我觉得这很有帮助也很有趣!非常感谢!我最初的查询是代码设计,计算平方根的惩罚是什么。看起来确实很小。虽然不是零,但我的测试让我感到困惑。我看到数据表明,这种运算比加法或乘法要贵几个数量级。
  • 不客气!说运算比加法或乘法贵几个数量级的数字是非常正确的。现代建筑的不同之处在于管道。在这里,我们充分利用了管道。由于所有迭代都可以并行运行,因此不同之处在于吞吐量倒数。例如,如果 add 的值为 1,sqrt 的值为 2,则即使 sqrt 的周期比 add 多 100 倍,sqrt 也只会慢 2 倍,因为大量的我们拥有的迭代。
  • 我认为相对性能也可能非常依赖于处理器。例如,英特尔 Fortran 编译器 (ifort) 有望更好地用于英特尔创建的芯片。
【解决方案2】:

您将在本文档中找到硬件平方根的成本:http://www.agner.org/optimize/instruction_tables.pdf

可以用不同的方式计算 sqrt。一般来说,它是一个只涉及加法和乘法运算的迭代过程。 通常 sqrt 计算为 sqrt(x) = x * (1/sqrt(x)),因为 (1/sqrt(x)) 可以比 sqrt(x) 更快地计算。

如果您使用 Haswell CPU,单条 SQRTSS 指令的延迟为 11 个单精度周期和 16 个双精度周期 (SQRTSD)。 在单精度中,与双精度相比,它需要更少的迭代来收敛到所需的精度。在同一个 CPU 上,有一个近似版本的 sqrt (RSQRTSS),延迟为 1 个周期,因此如果您要求进行积极的优化,您的编译器可能会选择生成此指令。

如果您需要多个独立的平方根,例如您的示例,代码可以由编译器自动向量化。存在向量化变体 VSQRTPS,其倒数吞吐量为 14。在这种情况下,您将获得大约 14/8 = 1.75 个周期/sqrt 的平均值。

参考资料:

【讨论】:

  • 好东西,谢谢!您的意思是它在单核中针对 SIMD 进行矢量化?因为据我所知,没有跨核心的自动多线程。
【解决方案3】:

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

【讨论】:

  • 这是一个答案,不过需要详细说明
  • 是的,可能是这样。如果我写入文件需要更长的时间,但是 i/o 可能会受到限制。我想我必须通过测试做出更多的思考。我使用 gfortran,我的笔记本电脑和操作系统是 64 位的,如果 ram 是 8gb。我不知道你的问题的确切答案。
  • 任何中等水平的优化编译器都会查看该程序,意识到它没有做任何工作并删除除打印语句之外的所有代码。我认为尝试为单个操作计时是浪费精力,特别是如果您不熟悉如何编写实际测试您正在寻找的内容的基准。
猜你喜欢
  • 2021-08-03
  • 1970-01-01
  • 2014-03-06
  • 2017-04-30
  • 2011-12-26
  • 2013-08-30
  • 2016-02-27
  • 1970-01-01
  • 2012-08-31
相关资源
最近更新 更多