Fortran Coder

查看: 1719|回复: 6
打印 上一主题 下一主题

[面向对象] 面向对象的性能问题

[复制链接]

43

帖子

13

主题

0

精华

专家

F 币
436 元
贡献
155 点
跳转到指定楼层
楼主
发表于 2023-10-29 03:53: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, 下载次数: 185)

Simply fortran优化选项

Simply fortran优化选项

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

运行时间对比

运行时间对比

Procedural.f90

12.68 KB, 下载次数: 10

面向过程

OOP.f90

15.27 KB, 下载次数: 9

面向对象

分享到:  微信微信
收藏收藏 点赞点赞 点踩点踩

43

帖子

13

主题

0

精华

专家

F 币
436 元
贡献
155 点
沙发
 楼主| 发表于 2023-10-29 03:55:52 | 只看该作者
不知道为什么,运行时间的图在网页上未显示完全。

147

帖子

42

主题

1

精华

宗师

F 币
1292 元
贡献
630 点
板凳
发表于 2023-10-29 08:58:11 来自移动端 | 只看该作者
实例变量这么多,传递self会有性能损耗。

147

帖子

42

主题

1

精华

宗师

F 币
1292 元
贡献
630 点
地板
发表于 2023-10-29 09:02:42 来自移动端 | 只看该作者
有时计算没必搞面向对象,过度的抽象有时会陷入误区

2033

帖子

12

主题

5

精华

论坛跑堂

臭石头雪球

F 币
1641 元
贡献
709 点

美女勋章热心勋章星光勋章新人勋章贡献勋章管理勋章帅哥勋章爱心勋章规矩勋章元老勋章水王勋章

5#
发表于 2023-10-29 11:15:03 | 只看该作者
首先 OOP 必然有性能上的牺牲。
牺牲具体有多大,可能与编译器,优化选项,算法,算例等诸多因素有关。

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

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

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

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

其次,simply fortran 内核就是 gfortran(minGW),所以它的表现应该是和 gfortran 一样的。
如果你感觉到它俩有明显差异,应该是编译选项的区别了。仔细检查两者用的编译选项是否一致。

43

帖子

13

主题

0

精华

专家

F 币
436 元
贡献
155 点
6#
 楼主| 发表于 2023-10-30 19:24:04 | 只看该作者
本帖最后由 愤怒的三炮 于 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更改为普通结构体

43

帖子

13

主题

0

精华

专家

F 币
436 元
贡献
155 点
7#
 楼主| 发表于 2023-10-30 19:26:01 | 只看该作者
weixing1531 发表于 2023-10-29 09:02
有时计算没必搞面向对象,过度的抽象有时会陷入误区

确实,但还是按耐不住要尝试一下。
您需要登录后才可以回帖 登录 | 极速注册

本版积分规则

捐赠本站|Archiver|关于我们 About Us|小黑屋|Fcode ( 京ICP备18005632-2号 )

GMT+8, 2024-12-22 22:09

Powered by Tencent X3.4

© 2013-2024 Tencent

快速回复 返回顶部 返回列表