You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升Fortran与Python接口代码的性能?

Fortran与Python对接的性能优化疑问

背景与初始问题

我正在研究Fortran代码与Python对接的可行性。了解f2py工具,但因无法成功调用LAPACK等外部库,转而使用ctypes实现对接。目前功能正常,但发现相同的矩阵逐元素乘法算法,Fortran代码运行速度慢于NumPy,这与预期不符。测试在Windows 10和Windows 11两台电脑上进行,使用winlibs.com提供的gcc-12.2.0-llvm-15.0.7-mingw-w64ucrt-10.0.0-r4版本gfortran,Python版本为3.9.13。

初始性能测试结果(1000×1000矩阵)

  • NumPy:2.77 ms ± 110 µs per loop(7次运行,每次100循环的均值±标准差)
  • Fortran:12.4 ms ± 314 µs per loop(7次运行,每次100循环的均值±标准差)

Fortran代码(libfortran3.f90)

subroutine prodotto(a, b, n) bind(c, name='prodotto')
  ! declarations
  use iso_c_binding, only: c_double, c_int

  integer(c_int), intent(in) :: n
  real(c_double), dimension(n,n), intent(in) :: b
  real(c_double), dimension(n,n), intent(inout) :: a
  
  do i = 1,n
    do j = 1,n
        a(i,j) = a(i,j)*b(i,j)
    end do
  end do
  
end subroutine prodotto

编译命令

gfortran -O3 -funroll-loops -ffast-math -fPIC -shared -o libfortran3.so libfortran3.f90

注:输入与输出复用同一矩阵,避免额外传递结果矩阵(否则性能可能更差)。

Python调用代码(Notebook中运行)

from ctypes import CDLL, POINTER, c_int, c_double
import numpy as np
import time
import sys

fortran = CDLL('./libfortran3.so')
fortran.prodotto.argtypes = [ POINTER(c_double), 
                                 POINTER(c_double), 
                                 POINTER(c_int)]
fortran.prodotto.restype = None
#
N = 10
A = np.random.rand(N,N)
B = np.random.rand(N,N)
#
print('With Numpy')
%timeit A*B
#
print('With Fortran')
A = np.asfortranarray(A, dtype=c_double)
B = np.asfortranarray(B, dtype=c_double)
Act = A.ctypes.data_as(POINTER(c_double))
Bct = B.ctypes.data_as(POINTER(c_double))
%timeit fortran.prodotto( Act, Bct, c_int(N) )

我知道自己的Fortran代码优化程度不及NumPy,但想了解是否有可修改之处以提升性能。


2023年4月3日更新

根据建议调整嵌套循环顺序后,性能得到大幅提升!将libfortran3.f90中的i与j交换后,1000×1000矩阵的测试结果如下:

  • NumPy:2.67 ms ± 148 µs per loop(7次运行,每次100循环的均值±标准差)
  • Fortran:1.01 ms ± 34.9 µs per loop(7次运行,每次1000循环的均值±标准差)

f2py测试尝试

我还尝试了f2py,代码位于libfortran1.f90:

subroutine prodotto(a, b, c, n) 
  ! declarations

  integer, intent(in) :: n
  real(kind=8), dimension(n,n), intent(in) :: a, b
  real(kind=8), dimension(n,n), intent(out) :: c
  
  do j = 1,n
    do i = 1,n
        c(i,j) = a(i,j)*b(i,j)
    end do
  end do
  
end subroutine prodotto

编译命令:

python -m numpy.f2py --compiler=mingw32 --fcompiler=gfortran -c libfortran1.f90 -m libfortran1

Notebook中运行结果:

  • f2py:12.3 ms ± 260 µs per loop(7次运行,每次100循环的均值±标准差)
    性能远低于纯NumPy。

LAPACK调用性能测试补充

我尝试编译调用LAPACK的Fortran代码(文件libfortran0.f90,LAPACK为自行编译):

subroutine risolvi(A,b,x,n) bind(c, name='risolvi')
  ! declarations
  use iso_c_binding, only: c_double, c_int

  integer(c_int), intent(in) :: n
  real(c_double), dimension(n,n), intent(inout) :: A
  real(c_double), dimension(n), intent(inout) :: b
  real(c_double), dimension(n), intent(out) :: x
  integer(c_int), dimension(n) :: pivot(n)
  integer(c_int) :: ok
  ! call LAPACK subroutine
  call DGESV(n, 1, A, n, pivot, b, n, ok) 
  x = b
  
end subroutine risolvi

编译命令:

gfortran -O3 -funroll-loops -ffast-math -fPIC -shared -L. -lblas  -llapack -o libfortran0.so libfortran0.f90

Python调用代码

from ctypes import CDLL, POINTER, c_int, c_double
import numpy as np
import time
import sys
import libfortran1

fortran = CDLL('./libfortran0.so')
fortran.risolvi.argtypes = [ POINTER(c_double), 
                                 POINTER(c_double), 
                                 POINTER(c_double), 
                                 POINTER(c_int)]
fortran.risolvi.restype = None
#
N = 1000
A = np.random.rand(N,N)
b = np.random.rand(N)
#
print('With Numpy')
%timeit np.linalg.solve(A,b)
#
print('With Fortran')
A = np.asfortranarray(A, dtype=c_double)
b = np.asfortranarray(b, dtype=c_double)
x = np.asfortranarray(np.zeros(N), dtype=c_double)
Act = A.ctypes.data_as(POINTER(c_double))
bct = b.ctypes.data_as(POINTER(c_double))
xct = x.ctypes.data_as(POINTER(c_double))
fortran.risolvi( Act, bct, xct, c_int(N) )
%timeit fortran.risolvi( Act, bct, xct, c_int(N) )

测试结果

  • N=1000时:
    • NumPy:13.6 ms ± 596 µs per loop(7次运行,每次100循环的均值±标准差)
    • Fortran:221 ms ± 9.74 ms per loop(7次运行,每次1循环的均值±标准差)
  • N=30时,Fortran更快:
    • NumPy:15.6 µs ± 244 ns per loop(7次运行,每次100000循环的均值±标准差)
    • Fortran:11.4 µs ± 237 ns per loop(7次运行,每次100000循环的均值±标准差)

想确认是大数组传递代码存在问题,还是ctypes本身侧重兼容性而非性能?


内容的提问来源于stack exchange,提问作者Federico Cluni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:07:56