【问题标题】:Python/Numpy versus Fortran running timesPython/Numpy 与 Fortran 运行时间
【发布时间】:2021-01-21 17:27:19
【问题描述】:

我有一个关于 Numpy /Python 和 Fortran 运行速度的问题。首先,我在 Fortran 中重新编写了一个正在运行的 Python 程序。它工作正常。但我意识到,与 Numpy 数组相比,使用更大的数组大小,Fortran 程序正在失去越来越多的速度。

这里有一些数字。对于低步长,Fortran(使用 Intel Fortran 编译器)需要 0.2 秒,而 Python 需要 5 秒。首先,当我看到这个时,我很高兴。但是后来我减小了步长,Fortran 程序用了 770 s,而 python 是 1450 s。这几乎是10倍的损失。而且我想如果我进一步减小步长,Python 会再次变得更快。太烂了。

我几乎查看了所有步骤。循环中的 Fortran 数组要慢 10 倍(步长小 10 倍),这在某种程度上是合乎逻辑的。但是 numpy 数组只慢了 2-3 倍。

有谁知道这些 numpy 函数是做什么的,它们不会线性地失去速度?在 Fortran 中有什么可以比拟的吗?

这是一个简短的示例,但整个代码有 1000 多列,所以没有人会读到这个。 psi 是一个复数数组,r 是一个实数/双精度数组,其长度取决于博士。首先是 Python 代码。

phi0= 4* pi * np.cumsum(np.cumsum(r * np.abs(chi)**2) * dr) * dr / r
phi0 += - phi0[-1] - N/r[-1]

dr=0.1 需要 0.00006s,dr=0.01 需要 0.00008s,dr=0.001 需要 0.0002s

这里是fortran代码:

 integer :: i,j,m
double precision :: sum2_j, pi=3.14159265359, N, dr, sum1_i
double precision, dimension (:), allocatable ::  sum1_array, phi, step1, r
complex(8), dimension(:), allocatable :: psi
!double precision :: start, finish

m=size(psi)
allocate (phi(m))
allocate (sum1_array(m))
allocate (step1(m))

!call cpu_time(start)

sum1_i=0
step1=r*abs(psi)**2
do i=1,size(psi)
sum1_i=sum1_i+step1(i)
sum1_array(i)=sum1_i*dr
end do


sum2_j=0
do j=1,size(phi)
sum2_j=sum2_j + sum1_array(j)
phi(j)=4*pi*sum2_j*dr/r(j)
end do


phi=phi - phi(size(phi))-N/r(size(r))

运行时间/使用 eclipse/photran(intel fortran 大约快 2 倍): dr=0.1: 0.0000008s, dr=0.01: 0.00006s, dr=0.001: 0.00045s

如您所见,Python 在较小的步长下几乎慢了 10 倍,但在较大的步长下甚至更快。此问题涉及 FORTRAN 代码中的两个循环。它并不特定于该代码。它发生在所有循环中。正如我所说,这只是一个例子。 到目前为止,没有什么我不会尝试的,因为我不明白为什么会发生这种情况。

【问题讨论】:

  • 当你的 Fortran 代码有 m=size(psi)psi 未分配时,我不会信任它。
  • 可编译的 MWE 几乎总是从这里的专家那里获得具体建议的最快方式。我知道将代码精简为几行会很麻烦,但值得付出努力。
  • @fortran1 在这种情况下,请显示minimal reproducible example
  • 并为 Fortran 和 Python 显示它。照原样,没有人可以确认您的时间或解释它们。其中一种可能性可能是 numpy 是通过并行化(simd 和 openmp 之一)而不是 Fortran 代码编译的。什么是编译器和编译选项? numpy 的版本是多少?
  • 那么谷歌错了——但更可能的是,错了。基本的numpy包至少有simdnumpy-mkl调用Intel MKL,它也有openmp。

标签: python numpy fortran


【解决方案1】:

也许我太累了,但你为什么需要两个循环?两个循环的迭代次数相同,您只需要对该索引的总和...

!personally I would define the used precission the following way:   
!integer, parameter:: singlep = selected_real_kind(6,37)!Single
integer, parameter:: doublep = selected_real_kind(15,307)!Double

!real(kind=doublep) :: sum2_j, pi=3.14159265359_doublep, N, dr, sum1_i,pi4dr

integer :: i,j,sizepsi
double precision :: sum2_j, pi=3.14159265359, N, dr, sum1_i
double precision, dimension (:), allocatable ::  sum1_array, phi, step1, r
complex(8), dimension(:), allocatable :: psi
!double precision :: start, finish
pi4dr=4.0*dr*pi


sizepsi=size(psi)
allocate (phi(sizepsi))

!call cpu_time(start)

sum1_i=0.0!you shold add the precision here like 0.0_doublep
sum2_j=0.0

do i=1,sizepsi !we already know how large psi is
    sum1_i=sum1_i+r(i)*abs(psi(i))**2
    sum2_j=sum2_j + sum1_i*dr
    phi(i)=pi4dr*sum2_j/r(i)
end do

phi=phi - phi(sizepsi)-N/r(sizepsi) !size(phi)=size(r)=size(psi)

由于您的示例代码不可运行,我不打算对其进行测试并比较结果。 编辑:将内部循环更改为稍快的版本。

【讨论】:

  • 谢谢,但令人惊讶的是,我认为您的构建需要更多时间,或者至少不会更快。 10 次测量的平均值表示:使用您的代码 (dr=0.01):5*10-5s,使用我的代码 4*10-5s。
  • 正如其他人已经说过的那样,如果没有适当的 MRE(和使用的编译器选项),测试和给出有用的答案是很困难的,据说我很无聊所以我添加了代码来测试时间(用gfortran-O2)。性能取决于 psi 的大小,对于小于 1E4 元素的小元素没有真正的区别,并且通常您的原始版本稍微快一些(例如:1E7 次迭代平均 1.5E-8 秒,对于 100 的向量大小)对于大向量情况不同,我的(更新的)版本更快(例如:对于 1E8 的向量大小,100 次运行平均 0.2 秒)。
  • 好的,谢谢,但是我要计算的向量大小在 1e5 左右,我看不出运行时间有什么区别。
猜你喜欢
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
相关资源
最近更新 更多