假设我们有一个Fortran函数(例如数学优化算法)作为输入,另一个Fortran函数:
myOptimizer(func)
现在,根据用户的选择,输入功能可以来自几个不同功能的列表。这个选择列表可以通过if-block实现:
if (userChoice=='func1') then
myOptimizer(func1)
elseif (userChoice=='func2') then
myOptimizer(func2)
elseif (userChoice=='func3') then
myOptimizer(func3)
end if
或者,我也可以定义函数指针,并将其写为,
if (userChoice=='func1') then
func => func1
elseif (userChoice=='func2') then
func => func2
elseif (userChoice=='func3') then
func => func3
end if
myOptimizer(func)
根据我使用带有O2标志的英特尔Fortran编译器2017的测试,第二种实现方式恰好比几个因素慢(比if-block实现慢4-5倍)。从软件开发的角度来看,我非常喜欢第二种方法,因为它会产生更加简洁和清晰的代码,至少在我的问题中,有一个固定的工作流程,具有不同的工作流输入功能。但是,在这个问题上,表现同样重要。
在所有Fortran代码中,间接函数调用是否会导致性能下降?或者它是编译器相关的问题?有没有使用间接函数调用而没有性能损失的解决方案?其他语言如C / C ++怎么样?
答案 0 :(得分:1)
这是一个纯粹的猜测,基于编译器通常如何工作以及可能解释4-5x性能差异的原因。
在第一个版本中,编译器可能会将myOptimizer()
内嵌到每个调用网站中,其中func1
,func2
和func3
内联到优化程序中,因此当它运行时没有实际的函数指针或函数调用发生。
间接函数调用并不比现代x86硬件上的常规函数调用昂贵得多。这是缺乏内联真正的伤害,特别是对于FP代码。在函数调用周围溢出/重新加载所有浮点寄存器是很昂贵的,特别是如果函数相当小的话。
即。你可能会伤到的是你的第二个版本说服编译器不要撤消间接。在C / C ++中也是如此。
手持编译器使快速asm可能意味着你必须以第一种方式编写它,除非有一个可以使用的配置文件引导优化选项,这可能会使编译器意识到这是一个热点,值得更加努力与源写的第二种方式。 (对不起,我不使用Fortran,我只知道英特尔C / C ++编译器的一些选项,从http://gcc.godbolt.org/上查看其asm输出与gcc和clang)
要查看我的假设是否正确,请检查编译器生成的asm。如果第一个版本实际上没有将函数指针传递给myOptimizer
的独立定义,但是第二个版本确实存在,那可能就是它的所有内容。
有关查看编译器输出的更多信息,请参阅How to remove "noise" from GCC/clang assembly output?。 Matt Godbolt的CppCon2017演讲:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”是阅读编译器输出的好介绍以及你可能想要的原因。