如果 operator!=() 使用 operator==() 的否定,我是否应该内联运算符 == 和 !=

Should I inline operators == and != in case when operator!=() uses negation of operator==()

我用这个简单的例子来说明我试图优化堆栈使用的问题。假设我有一个这样的结构:

// Something.h
struct Something {
  int val;

  bool operator==(const Something& rhs);
  bool operator!=(const Something& rhs);
};

// Something.cpp
bool Something::operator==(const Something& rhs) {
  return val == rhs.val;
}

bool Something::operator!=(const Something& rhs) {
  return !(*this == rhs);
}

调用 operator!=() 会将两个堆栈帧压入堆栈(一个用于 !=,另一个用于 ==)。 我应该内联 operator!=() 以便 == 和 != 使用相同数量的堆栈吗?

您对 inline 关键字的作用有误解。有关说明,请参阅此答案

回答你的问题,是的,“内联”可能更快,这就是编译器实际上会自动优化它的原因(只要你至少有 -O1):https://godbolt.org/z/9Kn3hE

您应该使用link-时间优化 (LTO),这样它们中的任何一个都可以完全内联到调用站点,尤其是当它像这样近乎微不足道的时候.

但是如果您不想使用 LTO 进行跨文件内联,那么可以将 operator != return !(*this == rhs); 定义 放在class 定义在 .h) 中,因此每个调用者都可以看到它,并且可以将其内联到刚刚包含 .h 的文件中。然后调用者的 asm 将调用相同的 operator== 定义,但以相反的方式使用结果。例如test al,al / jnz 而不是 jz 如果你在结果上分支。

如果您不使用 LTO 并且不使编译时内联的定义可见,那么最好的结果是编译器将 operator== 内联到 operator!= stand-编译那个 .cpp 时单独定义。然后你在机器代码中有两个相似大小的函数,它们仅相差一个布尔反转。这些函数(来自其他文件)的用户将调用一个或另一个,因此它们都在您的 I-cache / 代码足迹中占用 space。


例子

https://godbolt.org/z/e88nGj

// Something.h
struct Something {
  int val;

  bool operator==(const Something& rhs);
  bool operator!=(const Something& rhs) { return !(*this == rhs); }
};
// simulated #include for one-file demo purposes

// Some other .cpp file, operator== definition not visible.
int foo(Something &a, Something &b)
{
    if (a != b) {
        return a.val;
    } else { 
        return b.val;
    }
}

GCC -O3 for x86-64 (Godbolt) 编译如下:

foo(Something&, Something&):
        push    rbp
        mov     rbp, rsi
        push    rbx
        mov     rbx, rdi           # save the pointers in call-preserved regs
        sub     rsp, 8
        call    Something::operator==(Something const&)
        test    al, al                # set FLAGS from the bool retval
        cmovne  rbx, rbp              # select the right pointer
        mov     eax, DWORD PTR [rbx]  # and load from it

        add     rsp, 8                # epilogue
        pop     rbx
        pop     rbp
        ret

请注意,此代码调用 Something::operator==,它无法在编译时内联(它可以在 link 时使用 LTO)。它只是使用 cmovne 而不是 cmove 如果它调用了实际的单独 operator!=.

operator!= 内联到真正的零额外成本, 和对任一函数的所有调用都使用相同的独立定义,从而节省了代码占用空间。对性能有好处,尤其是当您的代码使用这两种运算符足以使其在缓存中保持热时。

当然,当 class 只是一个 int 时,让 operator== 内联也可以节省大量资金;完全不调用通常会好很多,因为不需要在某些东西周围保留寄存器。

(当然,在这种情况下,我的示例太简单了:如果它们 相等,那么它仍然可以 return a.val 因为它知道与 b.val 相同。因此,如果您取消注释 Godbolt link 中的 operator== 定义,foo 编译为 mov eax, DWORD PTR [rdi] / ret,甚至永远不会触摸 b.)