【问题标题】:Cross-module inlining of derived type accessor functions派生类型访问器函数的跨模块内联
【发布时间】:2017-07-26 15:33:07
【问题描述】:

在 C++ 中,通常会将可能被内联的小函数放入头文件中,以使内联成为可能,而无需求助于链接时优化或其他巫术。最常见的是,对于类的访问器方法(想想std::vector 上的operator[])。我在现代 Fortran 中遇到了一些类似的行为。

假设我有一个模块,它定义了一个带有一些私有数据的派生类型,并带有一个简单的访问器,如下所示:

module FooMod
type :: FooType
    integer,allocatable,private :: d(:)

    contains
        procedure,pass :: init
        procedure,pass :: get
        procedure,pass :: clear
endtype

contains
    subroutine init(this,n)
        class(FooType),intent(inout) :: this
        integer,intent(in) :: n
        integer :: i

        allocate(this%d(n))
        do i=1,n
            this%d(i)=i
        enddo
    endsubroutine

    function get(this,i) result(val)
        class(FooType),intent(in) :: this
        integer,intent(in) :: i
        integer :: val

        val = this%d(i)
    endfunction

    subroutine clear(this)
        class(FooType),intent(inout) :: this

        deallocate(this%d)
    endsubroutine
endmodule

现在我编写一个程序来使用访问器:

program testtype
use FooMod

type(FooType) :: foo
integer :: val

call foo%init(10)

val = foo%get(2)

write(*,*)val

endprogram

使用 gfortran 5.4.0 编译 -O3:

gfortran -c -O3 foo.f90
gfortran -O3 -S testfoo.f90 foo.o

产生这样的输出:

call    __foomod_MOD_get
leaq    16(%rsp), %rdi
movl    %eax, 12(%rsp)
movq    $.LC2, 24(%rsp)
movl    $11, 32(%rsp)
movl    $128, 16(%rsp)
movl    $6, 20(%rsp)
call    _gfortran_st_write

所以仍然有一个没有内联的调用。我明白了,因为 get() 例程的定义在不同的翻译单元中,与 C++ 标头的文本包含相比,内联它可能有点困难,但我认为这是 .mod 文件目的的一部分由编译器生成。

有没有办法让这样的小功能跨模块内联?如果没有,在数据封装的良好现代编程实践的背景下,这似乎是语言/实现的严重缺陷。

我尝试了 -flto 标志以查看是否有帮助,但这只是将 GIMPLE 作为 ascii 文本字段输出到“程序集”输出,因此很难判断它在做什么。

谢谢!

一些澄清:我知道 C++ 中的inline,它的真正含义,它有点用词不当,并且非常熟悉内联的概念;它是如何工作的,需要满足哪些条件,为什么没有标头会很困难,以及 LTO 在哪里适合。我主要的挫败感是,由于 Fortran 有正式的模块,允许编译器生成特定于实现的 .mod 文件,为什么这么难?为什么我们需要明确地求助于语言不可知的、链接时间(困难的)内联的大枪来做像内联类型绑定访问器函数这样简单的事情。编译器不能在 .mod 文件中存储 GIMPLE 代码,甚至函数文本并进行编译时内联吗?也许我遗漏了一些微妙之处。

【问题讨论】:

  • 您使用了哪些编译器选项? -flto 确实是完全必要的,它编译在不同的文件中,在不同的 gfortran 调用中。
  • 您可能需要查看与-flto here 相关的文档,以注意一些可能有助于您调查的相关选项(例如-fno-fat-lto-objects)。请注意,-flto 必须在编译和链接阶段都提供。
  • 它默认为-fno-fat-lto-objects。添加-ffat-lto-objects 将GIMPLE 字节码 常规程序集放入最终可执行文件的.s 文件中。不过,call 仍然存在。
  • 我想我属于后一类,如果有意义的话,希望编译器会这样做。我发现自己处于一种情况,它使世界上所有的东西都有意义,而编译器却没有这样做。我写了大量的 C++ 代码,并且不时检查编译器是否内联了这样的简单函数,而且几乎总是这样。无需踢腿。

标签: fortran gfortran


【解决方案1】:

在 C/C++ 中,您通常 #include 包含访问器函数定义的标头。所以在预处理之后,定义与调用站点在同一个 C/C++ 翻译单元中,并且函数可以被内联,即使是编译器的前端。

在 Fortran 中,PROGRAM、MODULE、external SUBROUTINE、external FUNCTION 和 BLOCK DATA 都定义了单独的编译单元。即使它们出现在同一个源文件中,编译器也可以将它们视为出现在单独的文件中。一些编译器会这样做,因此如果没有链接时间优化 (LTO),就无法内联。如果定义出现在与调用站点相同的源文件中,其他人会尝试内联。我知道只有一种编译器将模块过程的定义嵌入到它们的 .mod 文件中,这样它们就可以在使用模块的任何地方内联。

TL;DR 如果要在 Fortran 中内联这些函数,则必须启用 LTO。

【讨论】:

  • 您指的是哪个编译器将函数定义存储在 .mod 文件中?
  • Cray 编译器有一个选项“-O modinline”来允许内联模块过程。 (我相信没有 LTO。)请注意,这个问题已经不止一次出现在 Fortran 标准邮件列表中。例如here。免责声明:我从事 IBM 编译器的工作。我们要求启用 LTO 以将模块过程内联到其他编译单元中。
猜你喜欢
  • 2013-05-07
  • 2018-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-27
相关资源
最近更新 更多