获得Fortran阵列的优势

时间:2019-04-25 09:43:37

标签: fortran fortran90 fortran95

我目前正在对某些Fortran程序进行现代化改造,我想用签名

围绕旧样式的Fortran 77例程编写包装器

SUBROUTINE INITMATRIX( M , N , A , LDA) 
 INTEGER M, N, LDA 
 DOUBLE PRECISION A(LDA,*) 

对于该例程,我编写了一个包装,将Fortran 90样式矩阵作为输入

SUBROUTINE INITMATRIX_F90( A ) 
  DOUBLE PRECISION, INTENT(INOUT) :: A(:,:) 
  INTEGER :: M, N, LDA 
  M = SIZE(A,1) 
  N = SIZE(A,2) 
  LDA = SIZE(A,1) 
  CALL INITMATRIX(M, N, A, LDA)  
END SUBROUTINE

这很好用,除非我将一个片段传递给例程。例如,我有一个20 x 20的矩阵,我只想初始化前10行。然后我会打电话给

DOUBLE PRECISION A(20,20) 
CALL INITMATRIX_F90(A(1:10,1:20))

这会导致错误,因为我的包装器获取了错误的数组前导尺寸。在示例中,我使用LDA=10而不是LDA = 20。有没有办法访问数组的跨步/扩展来恢复前导维?关于ISO_Fortran_binding.h头文件与C的互操作性,该信息存储在数组描述符中。

为了可视化问题,下面是MWE对问题进行降级处理。

PROGRAM MAIN
    INTERFACE
        SUBROUTINE INITMATRIX_F90(A)
            DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
        END SUBROUTINE

    END INTERFACE

    DOUBLE PRECISION :: A(20,20)
    A = 1.0D0

    CALL INITMATRIX_F90(A(1:10,1:20))

END PROGRAM MAIN

SUBROUTINE INITMATRIX_F90( A )
    USE ISO_C_BINDING
    IMPLICIT NONE


    DOUBLE PRECISION, INTENT(INOUT), POINTER :: A(:,:)
    INTEGER :: M, N, LDA
    TYPE(C_PTR) :: LOC1, LOC2
    INTEGER*16 :: LOCX1, LOCX2
    CHARACTER*32 :: TMP
    M = SIZE(A,1)
    N = SIZE(A,2)

    WRITE(TMP, *) C_LOC(A(1,1))
    READ(TMP, *) LOCX1
    WRITE(TMP, *) C_LOC(A(1,2))
    READ(TMP, *) LOCX2

    LDA = (LOCX2-LOCX1) / C_SIZEOF(A(1,1))

    WRITE(*,*) "M = ", M
    WRITE(*,*) "N = ", N
    WRITE(*,*) "LOC = ", LOCX1, LOCX2
    WRITE(*,*) "LDA(COMPUTED) = ", LDA

END SUBROUTINE

(我知道接口中缺少该指针,只有在该指针中才能使C_LOC工作。)

输出为

 M =           10
 N =           20
 LOC =  140721770410864 140721770411024
 LDA(COMPUTED) =           20

显然,领先维度是通过肮脏的黑客程序正确计算的。 GNU Fortran编译器使用的内部结构,或ISO C <-> Fortran绑定(与GNU使用的结构不同)都包含信息,因此如何从Fortran中访问它们就不会有肮脏的技巧。

另一个MWE是围绕LAPACK的DLASET的以下包装:

PROGRAM MAIN
    INTERFACE
        SUBROUTINE INITMATRIX2_F90(A)
            DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
        END SUBROUTINE

    END INTERFACE

    DOUBLE PRECISION :: A(20,20)
    A = 0.0D0

    CALL INITMATRIX2_F90(A(1:10,1:20))

    WRITE(*,*) A(1:20,1)

END PROGRAM MAIN

SUBROUTINE INITMATRIX2_F90( A )
    USE ISO_C_BINDING
    IMPLICIT NONE
    DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
    INTEGER :: M, N, LDA
    EXTERNAL DLASET
    M = SIZE(A,1)
    N = SIZE(A,2)
    CALL DLASET( "All", M, N, 1.0D0, 1.0D0, A(1,1) , M)
END SUBROUTINE

这将在A的第一列中提供20个而不是10个和10个零。

1 个答案:

答案 0 :(得分:1)

恐怕您的现代化工作将导致彻底的困惑。如果您确实必须使用切片来调用INITMATRIX,请不要使用F90wrapper,否则您将一无所获。当然不是按照您的计划,那是非法的。

会发生什么

DOUBLE PRECISION, INTENT(INOUT) :: A(:,:) 

CALL INITMATRIX(M, N, A, LDA)  

当A不连续时,编译器将复制A并传递该副本。因此,尝试使用原始数组的描述符将毫无用处。即使它确实起作用,您最终得到的代码也会比原始代码差。

我建议或者对INITMATRIX本身进行现代化,或者只是按照到目前为止的方式直接调用它。


还有其他选项,例如仅传递第一个元素,然后传递步幅信息(通常在带有子数组数据类型的MPI中进行),但我不建议这样做。原来看起来更好。

CALL INITMATRIX(M, N, A(1,1), LDA) 

如果您实际上是在INITMATRIX_F90中进行此操作的,则应将其放入第一个INITMATRIX_F90示例中以明确说明。)

在新示例中,您所做的是获得每一列的前几个元素的地址的差异,实际上有时是这样做的。您可以做到,它应该可以工作。如果您使用1.使用通用扩展名LOC(以及可选的SIZEOF)或2.使用transfer()来获取整数值而不是I / O例程,则会更容易。请注意,一个8字节的整数就足够了,最好使用INTEGER(C_INTPTR_T)(或ptrdiff)。


在修正了问题POINTER并删除了不必要的内容后,请考虑您的MWE:

PROGRAM MAIN
    INTERFACE
        SUBROUTINE INITMATRIX_F90(A)
            DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)
        END SUBROUTINE
    END INTERFACE

    DOUBLE PRECISION :: A(20,20)
    A = 1.0D0
    print *,"loc in main",loc(A(1,1))
    CALL INITMATRIX_F90(A(1:10,1:10))
END PROGRAM MAIN

SUBROUTINE INITMATRIX_F90( A )
    IMPLICIT NONE
    DOUBLE PRECISION, INTENT(INOUT) :: A(:,:)

    print *,"LOC in INITMATRIX_F90:",loc(A(1,1))
    call external(A)
END SUBROUTINE

subroutine external(A)
  double precision :: A(*)
  print *,"LOC in external:", loc(A(1))
end subroutine

输出:

> ./a.out 
 loc in main      140721998532864
 LOC in INITMATRIX_F90:      140721998532864
 LOC in external:             37291664

如您所见,当将A传递给外部过程时,编译器会制作副本