【问题标题】:Inline member operators vs inline operators C++内联成员运算符与内联运算符 C++
【发布时间】:2012-05-20 03:21:24
【问题描述】:

如果我有两个结构:

struct A
{
    float x, y;
    inline A operator*(A b) 
    {
        A out;
        out.x = x * b.x;
        out.y = y * b.y;
        return out;
    } 
}

还有一个等价的结构

struct B
{
    float x, y;
}

inline B operator*(B a, B b) 
{
    B out;
    out.x = a.x * b.x;
    out.y = a.y * b.y;
    return out;
} 

您是否知道 B 的 operator* 有什么不同的编译方式,或者运行速度比 A 的 operator* 慢或快的任何原因(函数内部进行的实际操作应该是无关紧要的)?

我的意思是...将内联运算符声明为成员而不是成员,对实际函数的速度有任何一般影响吗?

我有许多不同的结构,目前都遵循内联成员运算符样式...但是我想将其修改为有效的 C 代码,而不是;所以在我这样做之前,我想知道性能/编译是否会有任何变化。

【问题讨论】:

标签: c++ performance struct inline operator-keyword


【解决方案1】:

按照您编写的方式,我希望B::operator* 运行速度稍慢。这是因为A::operator* 的“幕后”实现如下:

inline A A::operator*(A* this, A b) 
{ 
    A out;
    out.x = this->x * b.x;
    out.y = this->y * b.y;
    return out;
}

所以A 将指向其左侧参数的指针传递给函数,而B 必须在调用函数之前复制该参数。两者都必须复制其右侧参数。

如果您使用引用编写代码并使其 const 正确,则您的代码会更好,并且可能会为 AB 实现相同的代码:

struct A
{
    float x, y;
    inline A operator*(const A& b) const 
    {
        A out;
        out.x = x * b.x;
        out.y = y * b.y;
        return out;
    } 
}

struct B
{
    float x, y;
}

inline B operator*(const B& a, const B& b) 
{
    B out;
    out.x = a.x * b.x;
    out.y = a.y * b.y;
    return out;
}

您仍然希望返回对象,而不是引用,因为结果实际上是临时的(您没有返回修改后的现有对象)。


附录

但是,对于 B 中的两个参数都使用 const 传递引用,由于取消引用,它是否会比 A 有效地更快?

首先,当您拼出所有代码时,两者都涉及相同的取消引用。 (记住,访问this 的成员意味着指针解引用。)

但即便如此,这也取决于你的编译器有多聪明。在这种情况下,假设它查看您的结构并决定它不能将其填充到寄存器中,因为它是两个浮点数,因此它将使用指针来访问它们。因此,取消引用的指针案例(这是实现引用的方式)是您将得到的最好的。程序集看起来像这样(这是伪程序集代码):

// Setup for the function. Usually already done by the inlining.
r1 <- this
r2 <- &result
r3 <- &b

// Actual function.
r4 <- r1[0]
r4 <- r4 * r3[0]
r2[0] <- r4
r4 <- r1[4]
r4 <- r4 * r3[4]
r2[4] <- r4

这是假设一个类似 RISC 的架构(比如 ARM)。 x86 可能使用更少的步骤,但无论如何它都会被指令解码器扩展到这个级别的细节。关键是它都是寄存器中指针的固定偏移量取消引用,这几乎是最快的。优化器可以尝试变得更智能并跨多个寄存器实现对象,但这种优化器很难编写。 (虽然我有一个偷偷摸摸的怀疑,如果 result 只是一个未保留的临时对象,LLVM 类型的编译器/优化器可以轻松地进行优化。)

因此,由于您使用的是this,因此您有一个隐式指针取消引用。但是如果对象在堆栈上呢?没有帮助;堆栈变量变成堆栈指针(或帧指针,如果使用)的固定偏移解引用。因此,您最终会在某处取消引用指针,除非您的编译器足够聪明,可以获取您的对象并将其分散到多个寄存器中。

随意将-S 选项传递给gcc 以获取最终代码的反汇编,以了解您的实际情况。

【讨论】:

  • 谢谢,Mike... 我总是忘记使用 const 或 pass-by-reference。我的错误......但是,对于两个参数的 const 传递引用,在 B 中,由于取消引用,它会比 A 更快吗?
  • @Stefan:不,因为两者都涉及取消引用(一个取消引用 this 指针,另一个取消引用显式引用参数,但成本相同)。
  • 如果编译器可以证明传递引用/参数是仅加载参数,它可以优化指针/引用传递值。
【解决方案2】:

你真的应该把inline-ing 留给编译器。

也就是说,在类定义中定义的函数(如A 的情况)默认为inlineA::operator *inline 说明符没有用。

更有趣的情况是在类定义之外有成员函数定义。在这里,如果您想向编译器提供一个提示(它可能会随意忽略)这是经常使用的并且指令应该在调用者中内联编译,则需要 inline。

阅读 C++ FAQ 9

【讨论】:

  • 还要记住,编译器(疯狂地)被允许忽略inline。因此,您可能还想将inline 非成员函数声明为static,以防它对您执行此操作,这样您就不会在链接时出现多重定义符号错误。 (我说“疯狂”,因为这造成了我遇到的一种情况,#define 可以强制内联,而inline 函数不能,结果证明使用inline 的代码实际上更大并且需要更多堆栈。没人想到嵌入式的家伙。)
  • 请注意,我说的是“声明inline nonmember 功能为static”。不幸的是,static 关键字在两种情况下的含义截然不同。我的观点是关于避免多重定义符号错误,因为编译器决定不内联 nonmember 函数。另外,我在实践中也遇到过这个问题,所以并不是所有的编译器都像你描述的那样符合标准。
【解决方案3】:

这是我将如何编写结构:

struct A
{
    float x, y;
    A(float ax, float ay) : x(ax), y(ay) { }
    A operator*(const A& b) const { return b(x * b.x, y * b.y); } 
}

要回答这个问题,是的,将运算符编写为成员函数在某些情况下可能会稍微快一些,但不足以在代码中产生明显的差异。

一些注意事项:

  1. 不用担心使用 inline 关键字。优化编译器 自己决定什么和不内联。

  2. 使用初始化构造函数。这样做是因为他们改进了代码 可读性。睡得更好知道他们可以带小 性能优势。

  3. 尽可能频繁地通过 const 引用传递结构。

  4. 专注于编写具有良好风格的代码而不是快速。大多数代码是 足够快,如果不是,可能是因为某些原因 在算法或 IO 处理方面一头雾水。

【讨论】:

  • 您能否解释一下什么情况会导致潜在的(微小的)速度增加?如果没有,那没关系。
  • 一些编译器在某些情况下会倾向于通过寄存器传递“this”指针,但这只是意味着函数调用附近的一些其他汇编代码有点慢。所以:别担心。
猜你喜欢
  • 1970-01-01
  • 2013-11-29
  • 2012-12-22
  • 1970-01-01
  • 2011-05-07
  • 1970-01-01
  • 2017-08-30
  • 2012-01-04
  • 1970-01-01
相关资源
最近更新 更多