如果 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。
例子
// 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
.)
我用这个简单的例子来说明我试图优化堆栈使用的问题。假设我有一个这样的结构:
// 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。
例子
// 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
.)