Fortran Coder

标题: 面向对象的性能问题 [打印本页]

作者: 愤怒的三炮    时间: 2023-10-29 03:53
标题: 面向对象的性能问题
有幸看过雪球写的快速傅里叶变换程序,其中用到了面向对象。我也试着将曾经写的一小段代码用 OOP 重构,发现其性能远不如面向过程。


下面两个代码文件分别用面向过程(Procedural.f90)和面向对象(OOP.f90)计算波在平面内的传播状态,使用 ifort 和不同选项的 gfortran 来编译它们。
为了对比,两份代码除了变量前面的 self%,其余部分几乎一致。

结果显示,
1. 面向对象的性能远不如面向过程,尤其是 intel 编译器编译出来的程序;
2. gfortran 编译出来的程序,两种范式的差别没有 ifort 那么大,但基本也有 20% 的差距;
3. 特别地,Simply fortran中打开优化选项(如下图所示),差距缩小至 5%.

也有人遇到过类似的问题:
performance - Huge slowdown when packing arguments into derived type with pointers in Fortran - Stack Overflow
Speed loss using object oriented features !!! - Intel Community

我有几个疑问,想请前辈们指教:

1. 面向对象性能差这么多,是我写的代码的问题还是 fortran本身的问题?
        我之前没写过面向对象的 fortran 程序,可能是我实现方法的问题;但根据不同编译器的表现来看,也有可能是编译器及其优化选项的问题。

2. 如何改进,才能让性能提升?
        我不懂如何添加优化 flag,一般都是在 Linux 下用 ifort 直接编译,但 ifort 仿佛对 OOP 没有任何优化。

3. 各位前辈都是如何使用 OOP 的?或者说是使用何种范式来管理自己的代码的?


image-20231027183743-9i0hewi.png (34.15 KB, 下载次数: 190)

Simply fortran优化选项

Simply fortran优化选项

2023-10-28_205211.jpg (42.77 KB, 下载次数: 195)

运行时间对比

运行时间对比

Procedural.f90

12.68 KB, 下载次数: 10

面向过程

OOP.f90

15.27 KB, 下载次数: 9

面向对象


作者: 愤怒的三炮    时间: 2023-10-29 03:55
不知道为什么,运行时间的图在网页上未显示完全。
作者: weixing1531    时间: 2023-10-29 08:58
实例变量这么多,传递self会有性能损耗。
作者: weixing1531    时间: 2023-10-29 09:02
有时计算没必搞面向对象,过度的抽象有时会陷入误区
作者: fcode    时间: 2023-10-29 11:15
首先 OOP 必然有性能上的牺牲。
牺牲具体有多大,可能与编译器,优化选项,算法,算例等诸多因素有关。

计算密集型的函数过程,采用面向过程的确可能效率会明显高很多。

但OOP有它的优势,例如代码管理上的逻辑清晰(更符合人们对真实世界事物的分类感知),更重要的是,同一个类可以同时有多个对象。
彭国论的书上有个例子很有意思,面向过程模拟了一家银行的入账出账。
但是如果你需要同时模拟多家银行,那么 OOP 就显示出它管理效率上的优势了。
这种情况下,面向过程如果用结构体表示一家银行,每个函数过程同样的传递这个结构体,其效率也就和OOP传递self/this指针差不多了。

所以,你看到面向过程和OOP两者之间的性能差距,底层来自于对self/this指针的传递和带偏移的解引用(取this指针的成员)
但逻辑上,实际是“管理一个例程”和“管理可能多个例程”之间的差异

所以,当你面对一个选择,到底是用面向过程还是OOP,首先你要问自己,我是否需要同时管理这个类型的多个历程?
如果答案是一定不会,那么OOP并不是一个好的选择。
如果答案是可能会,那么可以大部分数据管理、整理、输入输出等函数使用OOP,少部分计算密集型的过程慎重考虑。

其次,simply fortran 内核就是 gfortran(minGW),所以它的表现应该是和 gfortran 一样的。
如果你感觉到它俩有明显差异,应该是编译选项的区别了。仔细检查两者用的编译选项是否一致。
作者: 愤怒的三炮    时间: 2023-10-30 19:24
本帖最后由 愤怒的三炮 于 2023-10-30 19:27 编辑
fcode 发表于 2023-10-29 11:15
首先 OOP 必然有性能上的牺牲。
牺牲具体有多大,可能与编译器,优化选项,算法,算例等诸多因素有关。

感谢雪球回复。

您的回答中,有一句我不是很理解:
面向过程如果用结构体表示一家银行,每个函数过程同样的传递这个结构体,其效率也就和OOP传递self/this指针差不多了。

我平时写代码经常会用到地震数据,用 type 结构体还是挺多的,但似乎没有察觉到传递结构体时,效率有什么损失。

作为对比,我修改了上面的 OOP.f90 代码,将对象结构体内部的函数指针都删除了,同时将函数过程中的 class 全部改为了 type。

这意味着该代码变成了传统的面向过程范式,每个函数过程都显式地传递结构体。

但运行效率和使用模块公共变量的 Procedural.f90 并无区别,这符合我的经验。

所以我的猜想是:仅对于我的代码而言,OOP 的性能损失理论上应该很小,但 ifort 出现了 50% 左右的性能损失,极有可能是编译器对 fortran OOP 的支持问题。

距 Fortran 2003 面向对象面世已有 20 年,但 github 上的开源项目中,使用这个特性的是少之又少,上次提到和我类似问题的帖子还是 10 年前,这也是我猜想编译器没动力优化的原因。

最近倒是看到一些用 OOP 写的库,比如
vmagnin/forcolormap: A small Fortran fpm library for colormaps (github.com)

但就像雪球说的,它们主要也是用来 “数据管理、整理、输入输出”,并非用于密集计算。

Type.f90

14.81 KB, 下载次数: 3

OOP更改为普通结构体


作者: 愤怒的三炮    时间: 2023-10-30 19:26
weixing1531 发表于 2023-10-29 09:02
有时计算没必搞面向对象,过度的抽象有时会陷入误区

确实,但还是按耐不住要尝试一下。




欢迎光临 Fortran Coder (http://bbs.fcode.cn/) Powered by Discuz! X3.2