3

如果我有两个结构:

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 代码,而不是;所以在我这样做之前,我想知道性能/编译是否会有任何变化。

4

3 回答 3

11

按照你写的方式,我希望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必须在调用函数之前制作该参数的副本。两者都必须复制其右侧参数。

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

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;
}

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


附录

但是,对于两个参数的 const 传递引用,在 B 中,由于取消引用,它是否会比 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 可能使用更少的步骤,但无论如何它都会被指令解码器扩展到这个级别的细节。关键是它都是寄存器中指针的固定偏移量取消引用,这几乎是最快的。优化器可以尝试变得更智能并跨多个寄存器实现对象,但这种优化器很难编写。(虽然我偷偷怀疑 LLVM 类型的编译器/优化器如果result只是一个未保留的临时对象,则可以轻松地进行优化。)

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

随意传递-S选项以gcc获取最终代码的反汇编,以查看您的案例中实际发生的情况。

于 2012-05-20T03:28:53.220 回答
3

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

也就是说,在类定义中定义的函数(与 的情况一样A)是inline默认的。说明inlineA::operator *是无用的。

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

阅读 C++常见问题解答 9

于 2012-05-20T03:31:23.193 回答
2

这是我编写结构的方式:

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 处理中的某些问题。

于 2012-05-20T03:46:05.967 回答