数组初始化优化

33

在编译以下代码片段时(clang x86-64 -O3

std::array<int, 5> test()
{
    std::array<int, 5> values {{0, 1, 2, 3, 4}};
    return values;
}

它生成了我所预期的典型汇编代码

test():                               # @test()
        mov     rax, rdi
        mov     ecx, dword ptr [rip + .L__const.test().values+16]
        mov     dword ptr [rdi + 16], ecx
        movups  xmm0, xmmword ptr [rip + .L__const.test().values]
        movups  xmmword ptr [rdi], xmm0
        ret
.L__const.test().values:
        .long   0                       # 0x0
        .long   1                       # 0x1
        .long   2                       # 0x2
        .long   3                       # 0x3
        .long   4                       # 0x4

然而对于小的数组,它似乎找到了一个窍门?

std::array<int, 3> test()
{
    std::array<int, 3> values {{0, 1, 2}};
    return values;
}

这里 是相应的汇编代码

test():                               # @test()
        movabs  rax, 4294967296
        mov     edx, 2
        ret

这个神奇的数字(4294967296)从哪里来?它是不是可以通过reinterpret_cast重新转换回一个int数组的值?


4
在Godbolt上,您可以将鼠标悬停在数字上以查看其十六进制表示。如果您经常查看汇编代码,您会习惯于"4294967..."约等于2^32,因此这是一个重要提示,让您应该查看十六进制以了解高位/低位32位的值。(或对于一些略小于2^32的数字,它实际上是一个负的32位整数。) - Peter Cordes
1个回答

40

在您的实现中,std::array<int, 3>宽度为96位。因此,ABI声明应该将其返回到RAX + RDX的低32位(也称为EDX)。

4294967296是2的32次方,在十六进制中为$1'0000'0000。因此,movabs将0存储在RAX的低序32位中,并将1存储在RAX的高序位中。mov将2存储在EDX中(这正是您想要的)。


糟糕!感谢Sasha提出修改建议,以及Caf的实际操作。 - Martin Bonner supports Monica
3
有趣的事实:使用BMI2指令集编译这段代码可能更优的方法是 mov edx, 2 然后使用 rorx rax, rdx, 33 来将 1<<1 转化为 1<<32movabs 指令太长了(10字节),解码速度慢,而且会占用额外的微操作缓存空间。 rorx 只有6个字节(3个字节的VEX + 操作码 + ModR/M + imm8),但它的立即数很小。它依赖于 mov 立即数,并且只能在Intel CPU的移位端口上运行,但通常这不是瓶颈(特别是在代码路径导致 ret 的情况下,不包含在紧密循环中)。 - Peter Cordes
@PeterCordes 我认为优化器可以原谅没有发现这个特定的可能性 - 它非常特定于特定的数据模式! - Martin Bonner supports Monica
clang会寻找像lea r64,[reg + disp8]这样的东西来创建第二个大常量,而不是2x movabs。例如,请参见64位整数的有符号饱和加法?,其中clang使用movabs rcx,9223372036854775807lea rax,[rcx + 1]在需要两者版本的函数中将INT64_MAXINT64_MIN分别实现为RCX和RAX中的值。https://godbolt.org/z/iD6Ml8即使使用`-Os`,GCC仍然使用2x movabs - Peter Cordes
GCC和clang在某些情况下都会寻找像https://godbolt.org/z/wctons这样的优化,例如使用`sub rax,120来创建第二个相邻常量,而不是使用另一个movabs`。然而,它们并不是非常擅长这样做,可能也不会考虑旋转的可能性:P - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接