Assembly 如何用重复的字节值填充64位寄存器 我用Visual C++ 2010和MASM(“快速调用”约定)做一些X64程序集。

Assembly 如何用重复的字节值填充64位寄存器 我用Visual C++ 2010和MASM(“快速调用”约定)做一些X64程序集。,assembly,x86,64-bit,masm,Assembly,X86,64 Bit,Masm,假设我有一个C++函数: extern "C" void fillArray(unsigned char* byteArray, unsigned char value); 指向数组的指针将以RCX为单位,字符值将以DL为单位 如何使用DL向RAX中填充值,以便如果我要mov qword ptr[RCX],RAX并按数组打印,所有值都将等于“char value” 请注意,我并没有试图编写我的编译器代码,我只是在学习。您可以乘以0x0101010101010101将最低的字节复制到所有其他字节

假设我有一个C++函数:

extern "C" void fillArray(unsigned char* byteArray, unsigned char value);
指向数组的指针将以RCX为单位,字符值将以DL为单位

如何使用DL向RAX中填充值,以便如果我要
mov qword ptr[RCX],RAX
并按数组打印,所有值都将等于“char value”


请注意,我并没有试图编写我的编译器代码,我只是在学习。

您可以乘以0x0101010101010101将最低的字节复制到所有其他字节中(假设其余字节一开始都为零),这有点烦人,因为没有
imul r64、r64、imm64
,但您可以这样做:

mov  rax, 0x0101010101010101 
imul rax, rdx                 ; at least as fast as  mul rdx  on all CPUs
如果
rdx
不是必需的形式(换句话说,如果它设置了一些额外的位),只需添加一个
movzx eax,dl
,并将常数移到RDX或其他寄存器中。(
movzx edx,dl
无法从英特尔CPU上消除mov中获益。)

如果您不喜欢代码大小(
movr64,imm64本身已经是10个字节),只需在数据段中保留该常量。

xor ebx, ebx
mov bl, dl   ; input in dl
mov bh, dl
mov eax, ebx
shl ebx, 16
or  ebx, eax
mov eax, ebx
shl rax, 32
or  rax, rbx ; output in rax
所以它可能比哈罗德的解决方案慢

您还可以查看编译器的程序集输出以获取以下代码

uint64_t s;
s = (s << 8)  | s;
s = (s << 16) | s;
s = (s << 32) | s;
正如您所看到的,这不是很有效,因为编译器总是使用64位寄存器,即使在不需要的时候。因此,为了使编译器更容易进行优化,只需像这样修改它

uint32_t s = (c << 8) | c;
s = (s << 16) | s;
return ((uint64_t)s << 32) | s;

uint32\u t s=(c因为您调用了程序“fillArray”,所以我假设您喜欢用字节值填充整个内存块。因此我对不同的方法进行了比较。它是32位masm代码,但在64位模式下结果应该相似。每种方法都使用对齐和未对齐的缓冲区进行了测试。结果如下:

Simple REP STOSB - aligned....: 192
Simple REP STOSB - not aligned: 192
Simple REP STOSD - aligned....: 191
Simple REP STOSD - not aligned: 222
Simple while loop - aligned....: 267
Simple while loop - not aligned: 261
Simple while loop with different addressing - aligned....: 271
Simple while loop with different addressing - not aligned: 262
Loop with 16-byte SSE write - aligned....: 192
Loop with 16-byte SSE write - not aligned: 205
Loop with 16-byte SSE write non-temporal hint - aligned....: 126 (EDIT)
使用以下代码的最简单的变体似乎在两种情况下都表现最好,并且代码大小也最小:

cld
mov al, 44h   ; byte value
mov edi, lpDst
mov ecx, 256000*4  ; buf size
rep stosb
编辑:它不是对齐数据的最快版本。添加了性能最好的MOVNTDQ版本,见下文

为完整起见,以下是其他例程的摘录-假设该值在以下情况之前扩展为EAX:

代表斯托德:

mov edi, lpDst
mov ecx, 256000
rep stosd
简单的同时:

mov edi, lpDst
mov ecx, 256000
.while ecx>0
    mov [edi],eax
    add edi,4
    dec ecx
.endw
不同的简单方式:

mov edi, lpDst
xor ecx, ecx
.while ecx<256000 
    mov [edi+ecx*4],eax
    inc ecx
.endw
SSE(NT、对齐、编辑):

我在这里上传了整个代码——组装需要来自hutch的MASM包


如果SSSE3可用,您可以使用
pshufb
将一个字节广播到寄存器的所有位置,而不是一系列
punpck
指令

movd    xmm0, edx
xorps   xmm1,xmm1      ; xmm1 = 0
pshufb  xmm0, xmm1     ; xmm0 = _mm_set1_epi8(dl)

如果你想学习一些MMX/SSE,有这样的说明。但在这种情况下,它可能会慢一些,因为它只针对一个值。如果你同时对多个值进行计算,SSE的性能会更好。关于常数乘法的那一点正是我想要的。对于q/a:广播的未来读者为memset(也称为fillArray)设置一个字节到SSE寄存器可能是更好的选择。首先使用整数指令将其广播到32b寄存器(例如,使用此
imul
技巧),然后执行
movd
可能有意义,或者使用带有全零控制掩码的pshufb(您可以使用pxor aka
\u mm\u setzero()
高效地生成它)。(
\u mm\u setzero\u si128()
是我所指的内在函数的全名。)出于兴趣,您在什么CPU上运行这些基准测试?我想知道SB/IB/Haswell是否会从使用非临时存储中看到类似的好处?我使用了一个AMD X4640 CPU,DDR3-RAM时钟为1333。谢谢-我将尝试在Haswell上运行您的代码,看看它是否会给出类似的结果。
movzx ebx,dl
总是比一个
xor-ebx,ebx
/
mov-bl,dl
。在任何CPU(AFAIK)上写入BH后,xor归零都不能避免部分reg合并惩罚,因此在任何Intel P6/SnB系列上都是不好的,但在AMD或Silvermont上是聪明的。其他新颖的方法包括
shrd-ebx,edx,8
两次或四次,在具有1周期延迟shrd的CPU上。在现代CPU上具有3个周期延迟的CPU会倍增,但至少对于1->4广播,我们不会超过这一点。@PeterCordes为什么
xor ebx,ebx
/
mov bl,dl
不工作?建议在零惯用异或之后,寄存器的上24位将以某种方式标记为零,以避免暂停。有什么不同?@Noah:
xor ebx,ebx
/
mov-bl,dl
确实像往常一样作为
movzx-ebx,dl
的2-uop版本工作。但是在这个答案的上下文中,在它后面加上
mov-bh,dl
,然后读取ebx仍然会导致部分寄存器暂停。我猜测使用xor/movb而不是movzx的原因是试图避免部分寄存器暂停合并处罚,但不幸的是,在P6系列或SnB系列上,没有办法写AH然后读取完整的注册。我知道我之前的评论可能会导致混乱。//@PeterCordes ahh,只是从xor ebx,ebx;mov bl,dl
+读取
ebx
。是的,写入
bh
does:)
movd xmm0,eax
punpckldq xmm0,xmm0    ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH
punpcklqdq xmm0,xmm0   ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH
mov ecx, 256000/4   ; 16 byte
mov edi, lpDst
.while ecx>0 
    movdqa xmmword ptr [edi],xmm0    ; movdqu for unaligned
    add edi,16
    dec ecx
.endw
movd xmm0,eax
punpckldq xmm0,xmm0    ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH
punpcklqdq xmm0,xmm0   ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH
mov ecx, 256000/4   ; 16 byte
mov edi, lpDst
.while ecx>0 
    movntdq xmmword ptr [edi],xmm0
    add edi,16
    dec ecx
.endw
movd    xmm0, edx
xorps   xmm1,xmm1      ; xmm1 = 0
pshufb  xmm0, xmm1     ; xmm0 = _mm_set1_epi8(dl)