当前位置: 首页 > 知识库问答 >
问题:

为什么GCC不能为两个Int32的结构生成最佳运算符==?

何承
2023-03-14
#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);
}

但令我惊讶的是,只有带有memcpymemcmp的代码会被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/seteal,但其他版本执行单独的32位操作。

struct alignas(uint64_t)point在参数位于寄存器中的by-value情况下仍然很有帮助,为GCC优化了两个naiveEqual版本,但不是bithack异或/或。(https://godbolt.org/z/ofga1f)。这给了我们关于GCC内部的任何提示吗?对齐没有帮助。

共有1个答案

元俊雅
2023-03-14

如果您“修复”对齐,则所有的汇编语言输出都是相同的(带有GCC):

struct alignas(std::int64_t) Point {
    std::int32_t x, y;
};

演示

需要注意的是,一些正确/合法的方法(如类型双关语)是使用memcpy,因此在使用该函数时进行特定的优化(或更加积极)似乎是合乎逻辑的。

 类似资料:
  • 为什么运算符只应该是4个字节却生成12个字节?当我引用变量时,这只是引用数组第一个索引的内存地址。实际上,我打印了第一个索引的内存地址,并将其与进行了比较,它们产生了相同的内存地址结果,这证实了它们都引用了数组的第一个索引,但是“array”产生了12个字节,而产生了4个字节。

  • 问题内容: 为什么需要添加“ L”字母以获得正确的长值?还有什么其他价值呢? 问题答案: 你的第一个值实际上是一个long(因为是,并且是,所以带值的值的结果就是一个long值。 但是第二个值是一个整数(因为你仅将一个值与一个值混用。所以结果将是一个整数。现在,所获得的结果超出了整数的实际范围。因此,在分配给该变量之前,被截断以适合有效的整数范围。 查看以下打印语句: 当你运行上面的代码时: 输出

  • 下面的代码通过使用特征向量作为容器或简单的C数组来实现相同的计算。它产生一个封闭的但不是位到位等效的结果。 最后的数学运算是。

  • 代码 产出(如预期) 1.post increment运算符()在表中具有最高的优先级。因此它肯定会在赋值运算符()之前执行。根据post增量规则,变量的值只有在执行该语句之后才会增加。 参见有rvalue 3,而不是变量本身,对吗?但是如果它带来了一个具有lvalue的变量,那么5将插入其中,在语句结束后,它的值将是6。这有什么问题,为什么做不到?

  • “...如果这不可行,RFC4122建议使用命名空间变体,如类型5 UUID。” 我计划使用Java生成UUID,并引用了API https://docs.oracle.com/javase/8/docs/API/Java/util/UUID.html 通过维基百科:

  • 我使用mapstruct在两个Calses之间进行映射:CandidateDTO和CandidateDTO。 我在这里的问题是,当映射时,所有DTO字段都为空,因为没有生成映射字段。 请帮忙。