获取器和设置器。是否存在性能开销?

46

我的 C++ 项目中有一个粒子系统引擎,这些粒子本身只是一些变量结构,没有函数。目前,每个粒子 (Particle) 都是通过直接访问其变量从其父类 (ParticleSystem) 更新的。例如:

particle.x += particle.vx;

然而,我正在考虑像这样使用getter和setter:

particle.setX(particle.getX()+particle.getVX());

我的问题是:与直接访问数据相比,调用getter和setter是否会有性能开销?

毕竟,我需要更新很多粒子...


2
设置器/获取器主要用于代码的可读性和可维护性。调用函数会比直接访问数据有性能开销。 - Mohammed H
4
Getter和Setter仅用于封装数据及对其访问的控制,不会带来性能开销。编译器可以将简单的Getter和Setter内联处理。 - Martin Kristiansen
4
我可以想象不出他们如何提高可读性,但他们确实使以后更容易改变实现方式。 - Ed S.
2
@MartinKristiansen:是的,你会希望如此,而且大多数时候你是正确的,但并非总是如此。最好检查一下是否在性能关键的紧密循环中调用函数。 - Ed S.
2
@所有人。显然,性能开销不会很大。但是在比较调用函数和直接访问数据时,我们可以看出即使开销不显著,直接访问数据也会更快。 - Mohammed H
显示剩余4条评论
5个回答

66

当没有被优化掉时,设置器和获取器会带来一定的性能开销 在进行链接时优化的编译器上,它们几乎总是会被优化掉。而对于不进行链接时优化的编译器,如果它们知道函数体(而不仅仅是原型),它将被优化掉。只需查找内联优化就可以了。

但是,您使用获取器和设置器的原因是,可能希望获取或设置该变量具有其他附加副作用。例如,在物理模拟中,更改对象的位置也会更改附近对象的位置等。

最后,在经过优化的代码环境中,获取器和设置器操作的开销非常小,除非代码很热门,否则不值得担心。如果代码很热门,只需将获取器或设置器移到头文件并进行内联即可。
但是,在长时间运行的科学模拟中,它可能会累积 - 但这需要数以百万计的调用次数。

因此,归纳起来,获取器和设置器的开销微不足道,而且非常值得,因为它允许您非常具体地指定对象的功能,并允许您进行任何更改的调度。


5
+1 表示内联(虽然其他部分也是正确的)。内联后忘记它。 - MPelletier
6
请注意,将函数内联是a)仅仅是一个建议,并且b)只有在源文件中包含所有信息时才有效,因此必须在头文件中进行。 - OmnipotentEntity
7
关于“微小”和“无足轻重”的评论:函数调用会消耗循环周期,在科学计算中(大量的get/set操作在长时间运行的循环中发生),如果没有进行优化,它们可能会花费您相当多的时间(wall time)。尽管如此,将getter/setter内联通常是为了未来代码灵活性的好主意。 - Tony K.
2
@TonyK。我同意这可能是必要的,但往往并不是。不过你说得很好,它确实会累加。在代码中始终使用分析来确定热点,并根据需要进行优化。 - OmnipotentEntity
3
近期的编译器可以在链接时进行内联,这意味着函数可以被内联,即使它的定义在实现文件中而不是头文件中。编辑:哦等等,我刚看到你是答案的作者!既然您在答案中提到链接时优化,我不理解您关于内联仅在定义在标头中时才可能的评论。 - Luc Touraille
显示剩余5条评论

16

我对此持有不同的观点,与之前的回答不同。

Getter和Setter表明你的类设计方式不够实用:如果你没有将外部行为与内部实现抽象化,那么使用抽象接口就没有意义,你可以使用普通的结构体。

考虑一下你真正需要的操作。几乎肯定不是直接访问位置和动量的x、y和z坐标,而是要将它们视为向量量(至少在大多数计算中,这是优化所需的)。因此,你需要实现基于向量的接口*,其中基本操作是向量加法、缩放和内积。不是分量访问;你可能有时也需要这样做,但这可以通过一个单一的std::array<double,3> to_posarray()成员或类似的方法来完成。

当内部组件 xy ... vz 无法从外部访问时,您可以安全地更改内部实现而不破坏模块外部的任何代码。这就是 getter/setter 的全部意义;但是,在使用它们时,您只能进行有限的优化:任何实际的实现更改都将使 getter 变得更慢。
另一方面,您可以通过 SIMD 操作、外部库调用(可能在加速硬件上,如 CUDA)等方式对基于向量的接口进行优化。像 to_posarray 这样的“批量获取器”仍然可以实现合理高效,但单变量 setter 则不能。

*这里的向量是指数学意义上的向量,而不是像std::vector那样。


4
获取器和设置器允许您的代码在将来更轻松地发展,如果获取和设置任务变得稍微复杂。大多数C++编译器足够聪明,可以inline这些简单方法并消除函数调用的开销。

2
这个问题可能有不同的答案,但我在这里分享我的想法。
对于性能来说,对于简单的POD类型,成本大多可以忽略不计。但是仍然存在成本,并且取决于你返回的类型。对于粒子而言,数据量不会太大。如果这是一个图像类(如OpenCV cv::Mat)或3D数据(如VTK中的PolyData),最好使用指针/迭代器处理setter/getter,而不是实际数据,以避免内存分配问题。
当您想要模板化事物时,setter/getter可以非常有用,以避免隐式类型转换。 setter/getter可以是访问私有/受保护成员的一种方式,这也允许您避免将x作为变量的通用名称。此外,setter/getter可以返回L值引用,使您可以执行 particle.getX() = 10.0;

1
particle.getX() = 10.0; - 但是在什么时候/何处需要这个? - SChepurin
3
当你不仅想打破封装性,而是要“可怕地”打破它时,该怎么办? - user
@Michael Kjörling,我认为这样更自然,您为什么说它会破坏封装性? - tomriddle_1234
1
如果直接暴露成员变量违反了封装(这是常见且合理的争论),那么如何通过具有返回对该变量引用的函数来避免破坏封装呢?而且在这种情况下,我们甚至没有谈论一个潜在复杂的类,而是一个原始值类型(这里写着保留我不知道C++术语的确切术语)。我绝对不希望 getX() 返回对内部成员变量的引用,这肯定不会减少混淆。 - user
但这是C++...,数百万粒子。如果这个类存在安全问题,我同意你的看法。 - tomriddle_1234

0
在这种情况下,您的计算功能意义可能是:

void Particle::updatePosition() { x += vx; y += vy; }

或者:

void Particle::updatePositionX() { x += vx; }

然后:

particle.updatePositionX();


你的回答可以通过添加额外支持信息来改进。请[编辑]以添加更多细节,例如引用或文档,以便其他人可以确认你的答案是否正确。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接