访问者是否会影响应用程序的性能?

时间:2011-03-26 03:00:59

标签: c++ performance

我想知道访问器的使用是否会显着影响应用程序的性能。假设我们有一个Point类,有两个私有字段。我们可以通过调用GetX()等公共函数来访问这些字段。

class Point
{
public:
    Point(void);
    double GetX();
    double GetY();
    void SetX(double x);
    void SetY(double y);

    ~Point(void);

private:
    double x,y;
};

但是,如果我们需要在很长一段时间内获得字段x的值(例如,如果我们处理图像),这种结构是否会影响应用程序的性能?也许将字段x和y公开会更快?

5 个答案:

答案 0 :(得分:5)

首先,这可能是过早优化,并且在一般情况下,访问者不是应用程序级瓶颈的来源。然而,他们并不是神奇的小精灵尘埃。它通常 不是访问者会损害性能的情况。有几点需要考虑:

如果实现是inline或者您有一个支持链接时优化的工具链,则可能会产生0影响。下面是一个示例,它可以让您在非常糟糕的编译器上获得绝对相同的性能。

class Point {
    public: double GetX() const;
    private: double x;
};
inline double Point::GetX() const { return x; }

如果实现不合适,那么您将增加函数调用的成本。如果,如你所说,函数被多次调用,那么至少代码或多或少保证在缓存中,但开销的相对百分比可能很高:执行函数调用的工作高于移动double的工作,并且有一个指针间接,因为该函数实际使用this作为参数。

如果实现既是可重定位库(Linux * .so或Windows * .dll)的外包的一部分,那么为了管理重定位,还会出现一个额外的间接

相对于x86 32位,x86-64硬件上的后两种成本都降低了;这么多,你不应该担心它。我不能谈论其他架构。

倒数第二,如果你有许多琐碎的getter和setter的琐碎对象,并且如果你没有配置文件引导的优化或链接时优化,由于大量的微小函数可能会有缓存效果。每个函数可能需要至少一个缓存行,并且这些函数不会以将常用部分组合在一起的方式自然地组织。除非您正在编写一个非常大规模的C ++项目或核心组件,例如KDE基础系统,否则这个成本应该是忽略

最终,不要担心。

答案 1 :(得分:4)

这些方法应始终由编译器内联,并且其性能与将其公开相同。您可以使用inline关键字来帮助编译器,但这只是一个提示。如果避免函数调用开销非常重要,请阅读生成的程序集。如果他们被内联你就可以了。否则你可能会考虑放松他们的知名度。

答案 2 :(得分:4)

在典型的情况下,不会,性能不会有差异(除非你公平地告诉编译器不要内联任何函数)。但是,如果允许它内联函数,则可能会为它们生成相同的汇编语言。

然而,应该被视为通过包含这些可憎的行为来破坏您的设计的借口。首先,一个类通常提供高级操作,所以(例如)你可以有一个move_relativemove_absolute,所以不要这样:

Point whatever;

whatever.SetX(GetX()+3);
whatever.SetY(GetY()+4);

...你会做这样的事情:

Point whatever;

whatever.move_relative(3, 4);
然而,有时候,将某些东西作为数据公开确实有意义并且运作良好。如果/当你打算这样做时,C ++已经提供了一种封装对数据的访问的好方法:一个类。它还为SetXXXGetXXX提供了预定义的名称 - 分别为operator=operator T。正确的方法就是这样:

template <class T>
class encapsulate {
    T value;
public:
    encapsulate(T const &t) : value(t) {}
    encapsulate &operator=(encapsulate const &t) { value = t.value; }
    operator T() { return value; }
};

使用它,您的Point类看起来像:

struct Point { 
    encapsulate<double> x, y;
};

这样,您希望公开的数据看起来就像是一样。同时,您可以通过将encapsulate更改为完成所需操作的内容来完全控制获取/设置值。

Point whatever;
whatever.x = whatever.x + 3;
whatever.y = whatever.y + 4;

虽然我没有在上面的演示模板中烦恼,但支持普通的复合赋值运算符(+=-=*=/=相当容易等等)。根据具体情况,消除其中许多因素通常很有用。例如,添加/减去X / Y坐标通常是有意义的 - 但乘法和除法经常不会,所以你可以添加+=-=,如果有人意外输入/=|=(仅举几个例子),他们的代码根本无法编译。

这还可以更好地执行您对数据所需的任何约束。使用私有数据和访问器/更改器,类中的其他代码可以(并且几乎不可避免地)以您不想要的方式修改数据。通过强制执行正确的约束,没有专门致力于什么的类,这个问题几乎被消除了。相反,类内外的代码都做了一个简单的赋值(或者根据具体情况使用了值)并且它自动地路由operator= / operator T - 代码中的代码不能绕过所需的任何检查。

由于您(显然)关注效率,我将补充说,这通常也不会产生任何运行时成本。事实上,作为一个模板,它在这方面具有轻微的优势。正常函数中的代码可以(即使只是偶然)以一种阻止内联扩展的方式重写,使用模板消除了 - 如果你试图以一种不同的方式重写它生成内联代码,使用模板根本不会编译。

答案 3 :(得分:0)

只要在头文件中定义函数,以便编译器可以内联它们就应该没有任何区别。但是,即使它们没有内联,你仍然不应该公开它们,除非分析表明它是一个重要的瓶颈并且公开变量会改善问题。公开变量会降低封装和可维护性。有关公共变量的更多信息,请参阅What good are public variables then?

上的答案

答案 4 :(得分:-1)

简短的回答是肯定的,这会影响性能。你是否会注意到差异是另一个问题,取决于你在访问器中有多少代码,等等。

但更重要的问题是,您是否需要使用访问器获得的收益?如果将字段设为公共字段,则会失去对其值的控制权。你想让x或y成为NaN吗?还是+ -infinity?将它们公开会使这种情况成为可能。

如果你稍后决定你的点类不接受双精度(可能你需要更高的精度或者不需要精度),那么直接访问这些字段会带来麻烦。虽然此更改可能还需要更改访问器,但设置者应该可以使用重载方法。你可能仍然可以使用双重的公开表示,而内部表示不是双重的(尽管这对于Point类来说不太可能,我想)。

在其他情况下,您可能希望对访问者和设置者产生副作用,使公共领域可以避免。也许你想为你的点改变时创建事件,但是如果这些字段是公共的,那么你的类将不知道值何时改变。

<强> ADDED

好的,所以我对我的“是”进行了掩饰,以便能够解决我觉得更重要的非性能问题,我们不予理解。

在许多情况下,“是”可能是正确的,因为它是不可察觉的。没错,使用内联和kick-ass编译器最终可能会得到相同的代码(假设有double GetX() { return x; }之类的访问器),但那里有很多ifs。编译器只会内联最终位于同一目标文件中的内容(通常是从单个代码文件创建的)。因此,您还需要一个kick-ass链接器来优化其他目标文件中的引用(当您到达链接器时,内联提示可能仍然不会保留在代码中)。因此,某些代码(但不一定是所有代码)可能最终都是相同的,但这只是在事实之后才能确认并且没有用处。

如果您担心图像处理,那么可能值得允许朋友类,以便您编码的图像类可以直接访问字段,但我再也不认为即使在这种情况下访问者也是如此将为您的运行时添加很多东西。