为什么这个简短的比较没有优化我的预期方式?

Leu*_*nko 8 c++ optimization assembly

我有一个复合索引类型,它由两个16位整数组成一个32位对象,设计用于传递并稍微像指针一样对待.但我偶然注意到我定义的比较运算符没有按照我预期的方式进行优化.

鉴于此缩减代码:

#include <cstdint>

struct TwoParter {
    std::uint16_t blk;
    std::uint16_t ofs;
};
static_assert (sizeof(TwoParter) == sizeof(std::uint32_t), "pack densely");

bool equal1 (TwoParter const & lhs, TwoParter const & rhs) {
    return lhs.blk == rhs.blk && lhs.ofs == rhs.ofs;
}

bool equal2 (TwoParter const & lhs, TwoParter const & rhs) {
    auto lp = reinterpret_cast <std::uint32_t const *> (&lhs);
    auto rp = reinterpret_cast <std::uint32_t const *> (&rhs);
    return *lp == *rp;
}
Run Code Online (Sandbox Code Playgroud)

GCC(编译器资源管理器上的7.1 )生成以下程序集(选项-m64 -std=c++11 -O3):

equal1(TwoParter const&, TwoParter const&):
        movzwl  (%rsi), %edx
        xorl    %eax, %eax
        cmpw    %dx, (%rdi)
        je      .L5
        rep ret
.L5:
        movzwl  2(%rsi), %eax
        cmpw    %ax, 2(%rdi)
        sete    %al
        ret
equal2(TwoParter const&, TwoParter const&):
        movl    (%rsi), %eax
        cmpl    %eax, (%rdi)
        sete    %al
        ret
Run Code Online (Sandbox Code Playgroud)

其中一个似乎比另一个做更多的工作.但我只是看不出它们是如何不同的:断言保证了结构的布局使得比较uint23_t 必须比较所有相同的数据,而不是uint16_t分别检查字段.更重要的是,这是x86,因此编译器已经知道情况就是如此.短路行为&&应该对输出不重要,因为它的右手操作数没有效果(并且编译器可以看到这个),并且由于没有其他有趣的事情发生,我无法想象为什么它会想要例如,延迟加载数据的后半部分.

&&&操作符替换它可以摆脱跳转但不会从根本上改变代码的作用:它仍然会生成两个独立的16位比较,而不是一次性比较所有数据,这表明可能是短路不是问题(尽管它确实提出为什么不编译一个相关的问题&&,并&要么以同样的方式-当然这两个中的一个应该是在这两种情况下,"好").

我感兴趣的是,根据Compiler Explorer,所有主要的编译器(GCC,Clang,Intel,MSVC)似乎都做了大致相同的事情.这减少了这是一个优化器监督的可能性,但我看不出的是我自己对此的评估实际上是错误的.

所以这有两个部分:

1)equal1真的做同样的事equal2吗?我在这里想念一些疯子吗?

2)如果确实如此,为什么编译器会选择不发出较短的指令序列?

我敢肯定,优化在这方面必须有一些编译器知道,因为他们会加快例如像其他更严重的代码更加合理有效memcmp搡的事情到向量寄存器一次比较更多的数据.

Jar*_*d42 11

对齐要求不一样,TwoParter具有相同的对齐方式std::uint16_t.

更改TwoParter

struct alignas(std::uint32_t) TwoParter {
    std::uint16_t blk;
    std::uint16_t ofs;
};
Run Code Online (Sandbox Code Playgroud)

为gcc 7.1生成相同的asm:

equal1(TwoParter const&, TwoParter const&):
        movl    (%rsi), %eax
        cmpl    %eax, (%rdi)
        sete    %al
        ret
equal2(TwoParter const&, TwoParter const&):
        movl    (%rsi), %eax
        cmpl    %eax, (%rdi)
        sete    %al
        ret
Run Code Online (Sandbox Code Playgroud)

演示