在 Fortran 中实现像快速插值一样的 numpy
Achieving numpy like fast interpolation in Fortran
我有一个数值例程,需要 运行 求解某个方程,其中包含几个嵌套的四个循环。我最初把这个例程写成 Python,使用 numba.jit 来达到可接受的性能。然而,对于大型系统,这种方法变得相当慢,所以我一直在将例程重写为 Fortran,希望能加快速度。但是我发现我的 Fortran 版本比 Python 中的第一个版本慢了 2-3 倍。
我认为瓶颈是在每个最内层循环调用的线性插值函数。在 Python 实现中,我使用 numpy.interp
,与 numba.jit
结合使用时速度似乎相当快。在 Fortran 中,我编写了自己的插值函数,它是
complex*16 function interp(x_dat, y_dat, N_dat, x)
implicit none
integer, intent(in) :: N_dat
real*8, dimension(N_dat), intent(in) :: x_dat
complex*16, dimension(N_dat), intent(in) :: y_dat
real*8, intent(in) :: x
complex*16 :: y, y1, y2
integer :: i, i1, i2, im
if(x <= x_dat(1)) then
y = y_dat(1)
else if(x >= x_dat(N_dat)) then
y = y_dat(N_dat)
else
im = MINLOC(DABS(x_dat - x), DIM=1)
if(x_dat(im) >=x ) then
i1 = im
i2 = im - 1
else
i1 = im + 1
i2 = im
end if
y1 = y_dat(i1)
y2 = y_dat(i2)
y = y1 + (x-x_dat(i1))*(y2 - y1)/(x_dat(i2) - x_dat(i1))
end if
interp = y
return
end function interp
请注意,我需要插入复杂的数据。如果我的诊断是正确的,这个函数比 numpy.interp
慢得多,因为必须在每个循环中调用插值,这大大降低了整个程序的速度。
有谁知道是否有办法在 Fortran 中实现类似于 Numpy 的插值速度?或者如果我上面显示的插值函数效率低得可怕?我还没有太多编写 Fortran 代码的经验。
谢谢!
猜测(如果你想让我们做得比猜测更好,请参阅@IanBush 的评论),这是行
im = MINLOC(DABS(x_dat - x), DIM=1)
这会占用您所有的时间,因为此行的大小为 O(N)
,大小为 x_dat
,而其他所有内容均为 O(1)
。
如果 x_dat
是线性间隔的,那么您可以将此行替换为
im = 1 + nint((N_dat-1)*(x-x_dat(1))/(x_dat(N_dat)-x_dat(1)))
或更好的是,完全跳过 im
并将 i1
和 i2
计算为
i1 = 1 + floor((N_dat-1)*(x-x_dat(1))/(x_dat(N_dat)-x_dat(1)))
i2 = i1 + 1
如果 x_dat
不是线性间隔的,但具有其他有用的属性,那么您希望尽可能使用这些属性来计算 im
。
我有一个数值例程,需要 运行 求解某个方程,其中包含几个嵌套的四个循环。我最初把这个例程写成 Python,使用 numba.jit 来达到可接受的性能。然而,对于大型系统,这种方法变得相当慢,所以我一直在将例程重写为 Fortran,希望能加快速度。但是我发现我的 Fortran 版本比 Python 中的第一个版本慢了 2-3 倍。
我认为瓶颈是在每个最内层循环调用的线性插值函数。在 Python 实现中,我使用 numpy.interp
,与 numba.jit
结合使用时速度似乎相当快。在 Fortran 中,我编写了自己的插值函数,它是
complex*16 function interp(x_dat, y_dat, N_dat, x)
implicit none
integer, intent(in) :: N_dat
real*8, dimension(N_dat), intent(in) :: x_dat
complex*16, dimension(N_dat), intent(in) :: y_dat
real*8, intent(in) :: x
complex*16 :: y, y1, y2
integer :: i, i1, i2, im
if(x <= x_dat(1)) then
y = y_dat(1)
else if(x >= x_dat(N_dat)) then
y = y_dat(N_dat)
else
im = MINLOC(DABS(x_dat - x), DIM=1)
if(x_dat(im) >=x ) then
i1 = im
i2 = im - 1
else
i1 = im + 1
i2 = im
end if
y1 = y_dat(i1)
y2 = y_dat(i2)
y = y1 + (x-x_dat(i1))*(y2 - y1)/(x_dat(i2) - x_dat(i1))
end if
interp = y
return
end function interp
请注意,我需要插入复杂的数据。如果我的诊断是正确的,这个函数比 numpy.interp
慢得多,因为必须在每个循环中调用插值,这大大降低了整个程序的速度。
有谁知道是否有办法在 Fortran 中实现类似于 Numpy 的插值速度?或者如果我上面显示的插值函数效率低得可怕?我还没有太多编写 Fortran 代码的经验。
谢谢!
猜测(如果你想让我们做得比猜测更好,请参阅@IanBush 的评论),这是行
im = MINLOC(DABS(x_dat - x), DIM=1)
这会占用您所有的时间,因为此行的大小为 O(N)
,大小为 x_dat
,而其他所有内容均为 O(1)
。
如果 x_dat
是线性间隔的,那么您可以将此行替换为
im = 1 + nint((N_dat-1)*(x-x_dat(1))/(x_dat(N_dat)-x_dat(1)))
或更好的是,完全跳过 im
并将 i1
和 i2
计算为
i1 = 1 + floor((N_dat-1)*(x-x_dat(1))/(x_dat(N_dat)-x_dat(1)))
i2 = i1 + 1
如果 x_dat
不是线性间隔的,但具有其他有用的属性,那么您希望尽可能使用这些属性来计算 im
。