这里使用 f2py 来实现 Python 和 Fortran 的混合编程。 f2py 是 NumPy 提供的工具,可将 Fortran 代码编译为 Python 可调用的模块,是 Python 中调用 Fortran 代码的最常用方法。
在使用 f2py 前可能需要安装:
pip install meson
pip install ninja
sudo apt-get update
sudo apt-get install gfortran
一、入门的例子
将 example.f90 文件和 a.py 文件放在同一个文件夹中。
example.f90 文件内容:
subroutine hello()
print *, "Hello from Fortran!"
end subroutine hello
a.py 文件内容:
import example
example.hello()
在当前文件夹中使用命令:
f2py -c -m example example.f90
说明:
- -c 编译(compile),表示要生成动态链接库。动态链接库的文件格式为: .so(Linux 系统)或 .pyd(Windows 系统)。在 Windows 系统下编译时可能需要额外的 DLL 文件,报错可能性比较大。这里推荐直接在 Linux 系统下测试。 在 Linux 中,.so 文件(Shared Object 共享对象文件)是标准的动态库格式。
- -m example 指定生成的 Python 模块名为 example(导入时用 import example)。
- example.f90 为输入的 Fortran 源代码文件。
编译后在文件夹中得到文件(大概的文件名):example.cpython-39-x86_64-linux-gnu.so
然后运行 Python 程序,调用这个模块:
python a.py
运行结果:
Hello from Fortran!
二、循环求和的例子
example.f90 文件内容:
subroutine sum_matrix(matrix, total)
implicit none
! 输入参数
real, intent(in) :: matrix(:,:) ! 假定形状数组
real, intent(out) :: total ! 输出总和
! 局部变量
integer :: i, j
! 双重循环计算总和
total = 0.0
do j = 1, size(matrix, 2) ! 获取列数
do i = 1, size(matrix, 1) ! 获取行数
total = total + matrix(i, j)
end do
end do
end subroutine sum_matrix
a.py 文件内容:
import example
matrix = [[1, 2], [3, 4]]
print(example.sum_matrix(matrix))
print('---')
# 时间对比
import numpy as np
import time
matrix = np.random.rand(5000, 5000)
start = time.time()
print(example.sum_matrix(matrix))
end = time.time()
print('Python + Fortran 循环求和时间:', end - start)
start = time.time()
sum_result = 0
for i0 in range(matrix.shape[0]):
for j0 in range(matrix.shape[1]):
sum_result += matrix[i0, j0]
print(sum_result)
end = time.time()
print('Python 循环求和时间:', end - start)
from numba import jit
@jit
def numba_for_sum_matrix(matrix):
sum_result = 0
for i0 in range(matrix.shape[0]):
for j0 in range(matrix.shape[1]):
sum_result += matrix[i0, j0]
return sum_result
start = time.time()
print(numba_for_sum_matrix(matrix))
end = time.time()
print('Python numba 循环求和时间:', end - start)
start = time.time()
print(np.sum(matrix))
end = time.time()
print('Python numpy.sum() 求和时间:', end - start)
编译和运行:
f2py -c -m example example.f90
python a.py
运行结果:
10.0
---
12497711.0
Python + Fortran 循环求和时间: 0.7034428119659424
12498084.4906657
Python 循环求和时间: 8.130049228668213
12498084.4906657
Python numba 循环求和时间: 1.0548856258392334
12498084.490664132
Python numpy.sum() 求和时间: 0.0256350040435791
大致结论:
- Python + Fortran 循环求和的时间明显比 Python 循环求和时间短,同时也比 Numba 的表现好些。
- 如果只是求和,那么 numpy.sum() 的表现最好,这是因为 numpy 核心部分主要基于 C 实现,以及做了极致的优化。
三、矩阵求逆的例子
可能需要安装 LAPACK/BLAS:
sudo apt update
sudo apt install liblapack-dev libblas-dev
通过以下命令可以查看版本:
apt show liblapack-dev libblas-dev
这样安装 LAPACK/BLAS 默认的是 Netlib 版本的。如果需要性能更好一些的,也可以考虑安装和配置 OpenBLAS 版本和 MKL 版本的,可能比较麻烦,这里暂不讨论。
example.f90 文件内容:
! 矩阵求逆子程序
subroutine inverse_matrix(A, Ainv)
implicit none
real(8), intent(in) :: A(:,:) ! 输入矩阵
real(8), intent(inout) :: Ainv(:,:) ! 输出逆矩阵
real(8), allocatable :: work(:)
integer, allocatable :: ipiv(:)
integer :: n, info
n = size(A,1)
allocate(ipiv(n), work(n))
Ainv = A ! 复制输入矩阵
call dgetrf(n, n, Ainv, n, ipiv, info) ! LU 分解
call dgetri(n, Ainv, n, ipiv, work, n, info) ! 计算逆矩阵
deallocate(ipiv, work)
end subroutine inverse_matrix
特别说明:上面 Fortran 代码中把求逆后的矩阵 Ainv 也设置为既是输出变量,又是输入变量,所以在下面 Python 的代码中,不再以函数的形式调用,而是以子程序的形式。之所以这么处理,是因为如果 Ainv(:,:) 只是作为输出变量,在这里会出现数组维度没有正确定义的报错。
a.py 文件内容:
import numpy as np
import example
A = [[1.0, 2.0, 3.0],
[0.0, 1.0, 4.0],
[5.0, 6.0, 0.0]]
Ainv_1 = np.asfortranarray(np.empty_like(A))
example.inverse_matrix(A, Ainv_1) # 调用 Fortran 子程序
print(Ainv_1)
Ainv_2 = np.linalg.inv(A)
print(Ainv_2)
print('---')
# 时间对比
A = np.random.rand(3000, 3000)
import time
start = time.time()
Ainv_1 = np.asfortranarray(np.empty_like(A))
example.inverse_matrix(A, Ainv_1) # 调用 Fortran 子程序
end = time.time()
print('Python + Fortran 求逆时间:', end - start)
start = time.time()
Ainv_2 = np.linalg.inv(A)
end = time.time()
print('Python np.linalg.inv() 求逆时间:', end - start)
说明:这里的 Ainv_1 变量作为 Fortran 中的输入输出变量,在调用之前可能需要用 np.asfortranarray() 做个转换,不然可能会报错。
编译和运行:
f2py -c -m example example.f90
python a.py
运行结果:
[[-24. 18. 5.]
[ 20. -15. -4.]
[ -5. 4. 1.]]
[[-24. 18. 5.]
[ 20. -15. -4.]
[ -5. 4. 1.]]
---
Python + Fortran 求逆时间: 10.027450561523438
Python np.linalg.inv() 求逆时间: 2.1545636653900146
大致结论:
- 使用 f2py 来混合编程时,gfortran 中 LAPACK/BLAS 可以成功调用。
- np.linalg.inv() 会比 Python + Fortran 求逆的表现更好些。
【说明:本站主要是个人的一些笔记和代码分享,内容可能会不定期修改。为了使全网显示的始终是最新版本,这里的文章未经同意请勿转载。引用请注明出处:https://www.guanjihuan.com】