一个同事向我展示了一些我认为没有必要的代码,但确实如此。我认为大多数编译器会认为这三种相等测试的尝试是等价的:
#include <cstdint>
#include <cstring>
struct Point {
std::int32_t x, y;
};
[[nodiscard]]
bool naiveEqual(const Point &a, const Point &b) {
return a.x == b.x && a.y == b.y;
}
[[nodiscard]]
bool optimizedEqual(const Point &a, const Point &b) {
// Why can't the compiler produce the same assembly in naiveEqual as it does here?
std::uint64_t ai, bi;
static_assert(sizeof(Point) == sizeof(ai));
std::memcpy(&ai, &a, sizeof(Point));
std::memcpy(&bi, &b, sizeof(Point));
return ai == bi;
}
[[nodiscard]]
bool optimizedEqual2(const Point &a, const Point &b) {
return std::memcmp(&a, &b, sizeof(a)) == 0;
}
[[nodiscard]]
bool naiveEqual1(const Point &a, const Point &b) {
// Let's try avoiding any jumps by using bitwise and:
return (a.x == b.x) & (a.y == b.y);
}
但令我惊讶的是,只有那些具有 memcpy
或 memcmp
的文件被 GCC 转换为单个64位比较文件。为什么?(https://godbolt.org/z/aP1ocs)
对于优化器来说,如果我在四个字节的连续对上检查相等性,那么在所有八个字节上的比较是相同的,这不是很明显吗?
为了避免分别对这两个部分进行布尔化,编译效率有所提高(减少了一条指令,并且没有对 EDX 的错误依赖) ,但仍然有两个单独的32位操作。
bool bithackEqual(const Point &a, const Point &b) {
// a^b == 0 only if they're equal
return ((a.x ^ b.x) | (a.y ^ b.y)) == 0;
}
GCC 和 Clang 在通过 价值传递结构时都错过了相同的优化(所以 a
在 RDI 中,b
在 RSI 中,因为这是 x86-64 System V 的调用约定将结构打包到寄存器中的方式) : https://godbolt.org/z/v88a6s。Memcpy/memcmp 版本都编译成 cmp rdi, rsi
/sete al
,但其他版本执行单独的32位操作。
令人惊讶的是,struct alignas(uint64_t) Point
在参数位于寄存器中的 by-value 情况下仍然有帮助,它为 GCC 优化了两个 naiveequals 版本,但没有优化反向 XOR/OR。(https://godbolt.org/z/ofGa1f).这是否给了我们任何关于海湾合作委员会内部的提示?排成一条直线对响声没有帮助。