The following是我必须在给定x
、z
和y
位置的八叉树分支内生成3D坐标的‘数组’(其1字节元素被打包到结果uint_fast64_t
中)的最小可重现代码示例:
#include <stdint.h>
void test(uint_fast64_t *const coord, const uint_fast8_t x, const uint_fast8_t z, const uint_fast8_t y) {
static const uint_fast64_t m = 0x2040810204081ULL, a = 0x101010101010101ULL;
*coord = (x * m & a) | (z * m & a) << 1 | (y * m & a) << 2;
}
看一下汇编,GCC似乎只生成m
常量的一个"变体",但生成a
常量的三个variants
,包括0x404040404040404
和0x202020202020202
.
test:
movabs rax, 567382630219905 ; 0x2040810204081
movzx edx, dl
movzx esi, sil
movzx ecx, cl
movabs r8, 144680345676153346 ; 0x202020202020202
imul rdx, rax
imul rsi, rax
imul rcx, rax
movabs rax, 289360691352306692 ; 0x404040404040404
add rdx, rdx
and rdx, r8
movabs r8, 72340172838076673 ; 0x101010101010101
and rsi, r8
sal rcx, 2
or rdx, rsi
and rcx, rax
or rdx, rcx
mov QWORD PTR [rdi], rdx
ret
无论出于什么原因,GCC似乎一直在将第<< 1
和第<< 2
位传播到这些掩码上,并将它们分开存储,而同一个掩码只需先进行and
位移位就可以使用.这就是令人困惑的地方.
另一方面,Clang将位移位完全传播到常量,因此程序集包含64位常量中的6个,但不包含与<< 1
和<< 2
对应的移位操作.这似乎是以大小为代价的速度优化.
但我对GCC的处理方式感到困惑.一些常量是‘折叠’的,而另一些则不是,以及它们没有提供任何可察觉的好处的方式.
我的问题是:
- 出于某种模糊的原因,先执行移位,然后再执行
and
掩码,即使是以在代码中存储额外常量为代价,也有一些好处吗? - 如果没有,有没有什么黑客或编译器标志可以用来绕过这一点,并迫使GCC首先简单地将其设置为
and
,然后进行移位,以避免存储这些常量?
这是一种"编译器会优化代码,忘了它"的情况.并不是真的起作用,因为我觉得这个‘优化’本身是有问题的.
我知道16字节的操作码"不多",但我仍然很好奇,为什么GCC会进行这种"优化",尽管看起来像是输给了一个外行人的眼睛.这甚至会发生在aggressive size optimizations岁的人身上.