【问题标题】:Array initialization optimization数组初始化优化
【发布时间】:2019-11-02 05:22:36
【问题描述】:

编译以下代码时sn -p (clang x86-64 -O3)

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

It produced 我期望的典型程序集

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;
}

This 是对应的程序集

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

那个神奇的数字 (4294967296) 是从哪里来的?这本质上是一个可以将reinterpret_cast 以某种方式返回到int 数组的值吗?

【问题讨论】:

  • 在 Godbolt 上,您可以将鼠标悬停在一个数字上以十六进制查看。如果您经常查看 asm,您将习惯 4294967... 大约为 2^32,因此您应该查看十六进制以查看高/低 32 位的巨大线索。 (或者对于低于 2^32 的数字,它实际上是一个负的 32 位整数。)

标签: c++ x86-64 compiler-optimization calling-convention abi


【解决方案1】:

std::array&lt;int, 3&gt; 在您的实现中是 96 位宽。因此,ABI 声明它应该在 RAX + RDX 的低 32 位(又名 EDX)中返回。

4294967296 是 232,十六进制是 $1'0000'0000。所以movabs在RAX的低32位存储0,在RAX的高位存储1。 mov 将 2 存储在 EDX 中(这正是您想要的)。

【讨论】:

  • 天啊!感谢 sasha 提出编辑建议,也感谢 caf 提出建议。
  • 有趣的事实:使用 BMI2 可能更优化的编译方式是 mov edx, 2 然后 rorx rax, rdx, 331&lt;&lt;1 创建 1&lt;&lt;32movabs 太大(10 字节)以至于解码速度很慢,并且会占用 uop 缓存中的额外空间。 rorx 是 6 个字节(3 字节 VEX + opcode + modrm + imm8),但它的立即数很小。它确实依赖于mov-immediate,并且只能在 Intel CPU 上的移位端口上运行,但通常这不是瓶颈(尤其是在导致ret 的代码路径中,不包含在紧密循环中)
  • @PeterCordes 我认为优化器没有发现这种特殊可能性是可以原谅的——它非常特定于特定的数据模式!
  • clang 确实会寻找像 lea r64, [reg + disp8] 这样的东西来创建第二个大常量而不是 2x movabs。例如参见 Signed saturated add of 64-bit ints? 其中 clang 使用 movabs rcx, 9223372036854775807lea rax, [rcx + 1] 分别在 RCX 和 RAX 中实现 INT64_MAXINT64_MIN,在需要两者的功能版本中。 godbolt.org/z/iD6Ml8 GCC 只使用 2x movabs 即使使用 -Os
  • GCC 和 clang 在某些情况下都在寻找类似的优化:godbolt.org/z/wctons 喜欢使用 sub rax, 120 创建第二个附近的常量,而不是使用另一个 movabs。不过,他们不是很擅长,是的,可能并没有寻找轮换的可能性:P
猜你喜欢
  • 2020-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多