是否有一些不明显的技巧可以用递增字节(小端字节序)填充AVX 512寄存器?也就是说,等效于以下代码:
__m512i make_incrementing_bytes(void) {
/* Compiler optimizes this into an initialized array in .rodata. */
alignas(64) char data[sizeof(__m512i)];
for (unsigned i = 0; i < sizeof(data); i++) {
data[i] = i;
}
return _mm512_load_si512(data);
}
我看到的唯一明显的方法(以及愚者用上面的代码生成的方法)是从内存中使用vmovdqa64
的通用方法--但是这个常数的熵足够低,看起来应该可以做得更好。
(我知道通常常量加载并不位于关键路径上,或者您有一个备用寄存器专门用于常量,以便能够重新加载它,但我很感兴趣这个指令集中是否隐藏了一些技巧。例如,对于一个具有全宽度寄存器乘法的指令集,您可以用0x 1填充每个字节,将寄存器平方,并将结果左移一位-但据我所知,这并不适合AVX 512。)
1条答案
按热度按时间qrjkbowd1#
我不认为有任何非常有效的方法来动态地处理这样一个序列,其中不同的元素有不同的值。64个不同的字节值是相当高的熵,如果你不能利用与前面元素的相似性。
广播4字节或8字节模式很容易(从mov-immediate到整数寄存器),或者从内存中提取16或32字节模式。或者使用
vpmovzxbd
(例如),“压缩”具有更宽元素的混洗常量的存储(字、双字或四字),或者返回到generate something on the fly,其中从全1字节的向量开始,每个元素都具有相同的值。但是,除非你是手工编写asm,否则编译器会通过内部函数不断地传播,所以你只能任由它们摆布。有些编译器足够聪明,使用广播加载,而不是将_mm512_set1_epi32(0x03020100)
扩展到64字节,但并不总是如此。没有指令对每个元素做不同的事情,乘法技巧限于64位块的宽度。
0x01010101
的平方是一个有趣的技巧,这可能是一个很好的起点,除了你可能直接从mov eax, 0x00010203
/vpbroadcastd xmm0, eax
开始(或ZMM)或vmovd xmm0, eax
、或64位x1M6 N1 x(10字节)/x1M7 N1 x(6字节),其比x1M8 N1 x/x1M9 N1 x(以得到x1M10 N1 x)加上x1M11 N1 x/x1M12 N1 x便宜。虽然AVX-512有
vpmullq
,而AVX 2没有,但它甚至没有扩展的64位=〉128位乘法。每个AVX-512指令至少为6个字节(4字节EVEX + opcode + modrm),因此如果您针对纯.text+.rodata大小进行优化,则会快速增加(这在循环之外可能不是不合理的)。您仍然不希望实际的循环每次存储4个字节,进行16次迭代,如
add eax, 0x04040404
/stosd
,即使在循环外也会比您希望的要慢。从
set1_epi32(0x03020100)
或64位或128位版本开始,仍然需要多个混洗和添加步骤来扩展到512位,并向广播结果的每个部分添加适量的0x 04、0x 08或0x 10。我想不出更好的方法,而且它仍然不够好用。使用一些AVX 2指令比ZMM节省了代码长度,除非我缺少一种节省指令的方法。
策略是在ZMM中创建
[ 0x30 repeating | 0x20 repeating | 0x10 repeating | 0x00 repeating]
,并将其添加到广播16字节模式中。尺寸:机器货号:0x 2c字节。常量:16 + 4 = 0x14。
**总计:0x 40 = 64字节,**等同于将整个文本常量放入内存。
屏蔽可能节省了向量指令,但代价是需要设置屏蔽寄存器值,这需要花费
mov eax, imm32
/kmov k1, eax
。因此,这可以节省大约10个字节,相当于RIP相对寻址模式下ZMM从. rodata加载到寄存器的大小。或者,这可以节省4个字节,相当于RIP相对寻址模式下
vpaddb zmm0, zmm0, zmm31
与vpaddb zmm0, zmm0, [vector_const]
之间的差异,具体取决于您对它执行的操作。我确认了这一点,并将GDB连接到SDE:
我只是不知道一个简洁的方法来编写程序集中要加载的常量,既简洁又清楚
(例如,
val: .rept 64 / .byte .-val / .endr
满足前者,但不满足后者。)这是GAS语法的一个巧妙的用法(当然,如果你想把所有内容放在一行上,
;
是语句分隔符)。在NASM语法中,将
%assign
放在%rep 64
中是很自然的方式,如NASM手册中使用%rep
展开循环的示例所示。在GAS中,与
.set
等价的东西是可能的。%xdefine
would be usable, too,尽管这会使汇编器每次都对增长的0+1+1+1+1+...
文本字符串求值。相反,您的想法在NASM语法中看起来是这样的,其中注解和标签名称提醒读者它是如何工作的。实际上我更喜欢这个版本而不是
%assign
版本;需要跟踪的事情就少了。使用
times
在一行中执行所有操作是行不通的:v2: times 16 db $-v2
会填入零,因为$-v2
在重复之前会评估为常数零。