我们有一个发展 Nvidia GPU 和 Intel Xeon Phi 的项目。主机代码和GPU代码用Fortran编写并由pgfortran编译。为了将我们的一些工作转移到 Phi,我们必须创建一个由 ifort 编译的共享库(静态链接无法工作)并从代码的 pgfortran 部分调用共享子例程。通过这样做,我们可以将数组从 pgfortran 代码部分卸载到可以与 Xeon Phi 通信的 intel fortran 共享库。
现在我尝试将包含可分配数组的派生类型从代码的 pgfortran 部分传递到 ifort 共享库。看起来有些问题。
这是一个简单的示例(此处没有 Xeon Phi 卸载指令):
来电者.f90:
program caller
type cell
integer :: id
real, allocatable :: a(:)
real, allocatable :: b(:)
real, allocatable :: c(:)
end type cell
integer :: n,i,j
type(cell) :: cl(2)
n=10
do i=1,2
allocate(cl(i)%a(n))
allocate(cl(i)%b(n))
allocate(cl(i)%c(n))
end do
do j=1, 2
do i=1, n
cl(j)%a(i)=10*j+i
cl(j)%b(i)=10*i+j
end do
end do
call offload(cl(1))
print *, cl(1)%c
end program caller
称为.f90:
subroutine offload(cl)
type cell
integer :: id
real, allocatable :: a(:)
real, allocatable :: b(:)
real, allocatable :: c(:)
end type cell
type(cell) :: cl
integer :: n
print *, cl%a(1:10)
print *, cl%b(1:10)
end subroutine offload
生成文件:
run: caller.o libcalled.so
pgfortran -L. caller.o -lcalled -o $@
caller.o: caller.f90
pgfortran -c caller.f90
libcalled.so: called.f90
ifort -shared -fPIC $^ -o $@
注意这里的“
cl%a(1:10)
”,没有“(1:10)
”,就不会打印任何内容。
这段代码最终打印出了
cl(1)%a
中的元素,然后在下一行中遇到了分段错误,我试图打印出数组 cl(1)%b
。
如果我将“
cl%a(1:10)
”更改为“cl%a(1:100)”,并删除“print *, cl%b(1:10)
”。它将给出以下结果:
我们可以发现b数组中的元素在那里,但我只是无法通过“
cl%b(1:10)
”获取它们。
我知道这可能是不同编译器的派生类型结构不同造成的。但我真的想要一种可以在编译器之间传递这种派生类型的方法。有什么解决办法吗?
谢谢!
编译器的 ABI 可能不同。您不应该直接传递结构,而应在子例程内构建它们并使用指针,您应该将其作为
type(c_ptr)
或假定大小的数组传递(但可能会发生复制!)。
Fortran 2003 中与 C 的互操作性不仅仅意味着与 C 交互,还意味着与 C 互操作的任何其他编译器。它可以是不同的 Fortran 编译器。
请注意,在多个地方声明相同类型并将其用作相同类型是违反 Fortran 规则的,除非类型是
sequence
或 bind(C)
。这是您的程序不符合标准的另一个原因。
称为.f90:
subroutine offload(cl_c)
use iso_c_binding
type, bind(C) :: cell_C
integer :: id
integer :: na, nb, nc
type(c_ptr) :: a,b,c
end type cell_C
type cell
integer :: id
real, pointer :: a(:)
real, pointer :: b(:)
real, pointer :: c(:)
end type cell
type(cell) :: cl
type(cell_C) :: cl_C
integer :: n
cl%id = cl_C%id
call c_f_pointer(cl_C%a, cl%a, [cl_c%na])
call c_f_pointer(cl_C%b, cl%b, [cl_c%nb])
call c_f_pointer(cl_C%c, cl%c, [cl_c%nc])
print *, cl%a(1:10)
print *, cl%b(1:10)
end subroutine offload
来电者.f90:
program caller
use iso_c_binding
type, bind(C) :: cell_C
integer :: id
integer :: na, nb, nc
type(c_ptr) :: a,b,c
end type cell_C
type cell
integer :: id
real, allocatable :: a(:)
real, allocatable :: b(:)
real, allocatable :: c(:)
end type cell
integer :: n,i,j
type(cell),target :: cl(2)
type(cell_c) :: cl_c
n=10
do i=1,2
allocate(cl(i)%a(n))
allocate(cl(i)%b(n))
allocate(cl(i)%c(n))
end do
do j=1, 2
do i=1, n
cl(j)%a(i)=10*j+i
cl(j)%b(i)=10*i+j
end do
end do
cl_c%a = c_loc(cl(1)%a)
cl_c%b = c_loc(cl(1)%b)
cl_c%c = c_loc(cl(1)%c)
cl_c%na = size(cl(1)%a)
cl_c%nb = size(cl(1)%b)
cl_c%nc = size(cl(1)%c)
cl_c%id = cl(1)%id
call offload(cl_c)
print *, cl(1)%c
end program caller
使用 gfortran 和 ifort:
>gfortran called.f90 -c -o called.o
>ifort caller.f90 -c -o caller.o
>ifort -o a.out called.o caller.o -lgfortran
>./a.out
11.0000000 12.0000000 13.0000000 14.0000000 15.0000000 16.0000000 17.0000000 18.0000000 19.0000000 20.0000000
11.0000000 21.0000000 31.0000000 41.0000000 51.0000000 61.0000000 71.0000000 81.0000000 91.0000000 101.000000
0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00
0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00
这里不需要动态库。
为了获得 100% 理论上的可移植性,可以使用
c_int
、c_float
...格式可能会更好等等,但你明白了。
您还可以重载
cell
和 cell_C
之间的分配以简化转换。