为什么没有特定的寄存器来访问寄存器的其他部分(16-32)?
像啊或al一样访问ax寄存器的8位部分.

我试图从数组中包装一个带有32个字符的__m256i变量,并由indices指定.这是我的代码:
char array[]; // different array every time.
uint16_t offset[32]; // same offset reused many times
_mm256_set_epi8(array[offset[0]], array[offset[1]], array[offset[2]], array[offset[3]], array[offset[4]], array[offset[5]], array[offset[6]], array[offset[7]],
array[offset[8]],array[offset[9]],array[offset[10]],array[offset[11]], array[offset[12]], array[offset[13]], array[offset[14]], array[offset[15]],
array[offset[16]],array[offset[17]], array[offset[18]], array[offset[19]], array[offset[20]], array[offset[21]], array[offset[22]], array[offset[23]],
array[offset[24]],array[offset[25]],array[offset[26]], array[offset[27]], array[offset[28]], array[offset[29]], array[offset[30]],array[offset[31]])
Run Code Online (Sandbox Code Playgroud)
使用相同的偏移和不同的数组将多次调用此函数.但根据我的测试,我不认为它是最佳的.有什么想法改进吗?
我刚刚查看了彼得·科德斯(Peter Cordes)的回答,他说,
如果读取标志,则部分标志停顿会发生,如果它们确实发生的话。P4永远不会有部分标志停顿,因为它们永远不需要合并。相反,它具有错误的依赖关系。几个答案/评论混淆了术语。它们描述了一个错误的依赖关系,但随后将其称为部分标志停顿。这是由于仅写入一些标志而导致的速度下降,但是术语“部分标志停顿”是指必须合并部分标志写入时在SnB之前的Intel硬件上发生的情况。英特尔SnB系列CPU插入一个额外的uop来合并标志而不会停顿。Nehalem和更早的失速约7个周期。我不确定AMD CPU会受到多大的损失。
我感觉我还不明白什么是“部分国旗摊位”。我怎么知道一个人发生了?除了读取标志的某些时间之外,什么触发事件?合并标志是什么意思?在什么情况下会“写一些标志”,但不会发生部分标志合并?我需要了解哪些有关旗位的知识才能理解它们?
我正在尝试针对特定的 Kaby Lake CPU (i5-7300HQ) 优化以下子例程,理想情况下,与原始形式相比,代码速度至少快 10 倍。该代码在 16 位实模式下作为软盘式引导加载程序运行。它在屏幕上显示一个十位数的十进制计数器,从 0 - 9999999999 计数然后停止。
我查看了 Agner 的微体系结构和汇编优化指南、 指令性能表和英特尔的优化参考手册。
到目前为止,我能够做的唯一明智的优化是将loop指令交换为dec + jnz,在此处进行解释。
另一种可能的优化可能是交换lodsbfor mov + dec,但我发现的关于它的信息一直存在冲突,有些人说它有一点帮助,而另一些人则认为它实际上可能会损害现代 CPU 的性能。
我还尝试切换到 32 位模式并将整个计数器保留在一个未使用的寄存器对中以消除任何内存访问,但在读入一点后我意识到这十位将立即被缓存,并且 L1 缓存之间的延迟差异和寄存器只有大约三倍,所以绝对不值得以这种格式使用计数器的额外开销。
(编者注:add reg延迟为 1 个周期,add [mem]延迟约为 6 个周期,包括 5 个周期的存储转发延迟。如果[mem]像视频 RAM 那样不可缓存,则更糟。)
org 7c00h
pos equ 2*(2*80-2) ;address on screen
;init
cli
mov ax,3
int 10h
mov …Run Code Online (Sandbox Code Playgroud) 我一直在尝试在我的 Linux 操作系统上学习 32 位 Intel x86 nasm 语法程序集,我遇到了一个关于四个通用 32 位寄存器的问题。
从我一直思考,EAX是,本来要使用的32位寄存器与16位寄存器AX,其中又分为啊(高8位)和人(低8位)。ebx、ecx 和 edx 也是如此。
然而,在阅读了一篇快速文章后,我变得有点困惑。
32 位寄存器是否由 16 位寄存器(依次由两个 8 位寄存器组成)加上额外的 16 位组成?
到目前为止,我在谷歌上读到的所有结果都说明了它们的用途,而不是它们的实际组成。
我有一些在Release版本中编译的未知C++代码,因此它已经过优化.我正在努力的一点是:
xor al, al
add esp, 8
cmp byte ptr [ebp+userinput], 31h
movzx eax, al
Run Code Online (Sandbox Code Playgroud)
这是我的理解:
xor al, al ; set eax to 0x??????00 (clear last byte)
add esp, 8 ; for some unclear reason, set the stack pointer higher
cmp byte ptr [ebp+userinput], 31h ; set zero flag if user input was "1"
movzx eax, al ; set eax to AL and extend with zeros, so eax = 0x000000??
Run Code Online (Sandbox Code Playgroud)
我不关心第2行和第3行.由于流水线的原因,它们可能按此顺序存在,而恕我直言与EAX无关.
但是,我不明白为什么我会首先清除AL,以便稍后清除EAX的其余部分.结果将恕我直言EAX = 0,所以这也可能
xor eax, eax
Run Code Online (Sandbox Code Playgroud)
代替.这段代码的优势或"优化"是什么? …
我可以使用MOV指令将存储在内存中的数据项移动到我选择的通用寄存器中.
MOV r8, [m8]
MOV r16, [m16]
MOV r32, [m32]
MOV r64, [m64]
Run Code Online (Sandbox Code Playgroud)
现在,不要拍我,但怎么是下面的实现:MOV r24, [m24]?(我很欣赏后者不合法).
在我的例子中,我想移动字符"Pip",即0x706950h,以注册rax.
section .data ; Section containing initialized data
14 DogsName: db "PippaChips"
15 DogsNameLen: equ $-DogsName
Run Code Online (Sandbox Code Playgroud)
我首先认为我可以分别移动字节,即首先是一个字节,然后是一个字,或者是它们的某种组合.但是,我不能引用的"顶半" eax,rax,所以这倒下的第一个障碍,因为我最终会在写入任何数据首先被感动.
我的解决方案
26 mov al, byte [DogsName + 2] ; move the character “p” to register al
27 shl rax, 16 ; shift bits left by 16, clearing ax to receive characters “pi”
28 mov ax, word [DogsName] …Run Code Online (Sandbox Code Playgroud) 我在IvyBridge上.我发现jnz内循环和外循环中的性能行为不一致.
以下简单程序有一个固定大小为16的内部循环:
global _start
_start:
mov rcx, 100000000
.loop_outer:
mov rax, 16
.loop_inner:
dec rax
jnz .loop_inner
dec rcx
jnz .loop_outer
xor edi, edi
mov eax, 60
syscall
Run Code Online (Sandbox Code Playgroud)
perf工具显示外循环运行32c/iter.它表明jnz需要2个周期才能完成.
然后我在Agner的指令表中搜索,条件跳转有1-2"倒数吞吐量",注释"如果没有跳转就快".
在这一点上,我开始相信上述行为是以某种方式预期的.但为什么jnz在外循环中只需要1个循环来完成?
如果我.loop_inner完全删除部件,外部循环运行1c/iter.行为看起来不一致.
我在这里缺少什么?
perf上述程序的结果带命令:
perf stat -ecycles,branches,branch-misses,lsd.uops,uops_issued.any -r4 ./a.out
Run Code Online (Sandbox Code Playgroud)
是:
3,215,921,579 cycles ( +- 0.11% ) (79.83%)
1,701,361,270 branches ( +- 0.02% ) (80.05%)
19,212 branch-misses # 0.00% of all branches ( +- 17.72% ) (80.09%)
31,052 lsd.uops ( …Run Code Online (Sandbox Code Playgroud) 测试 AL 中的字节是否为零/非零通常哪个更快?
TEST EAX, EAXTEST AL, AL假设前面的"MOVZX EAX, BYTE PTR [ESP+4]"指令加载了一个带有零扩展的字节参数到 EAX 的其余部分,从而防止了我已经知道的组合值惩罚。
因此 AL=EAX 并且读取 EAX 不会受到部分寄存器的影响。
直观上,仅检查 AL 可能会让您认为它更快,但我敢打赌,对于 >32 位寄存器的字节访问,需要考虑更多的惩罚问题。
任何信息/细节表示赞赏,谢谢!
TL;DR我有一个循环需要 1 个周期才能在 Skylake 上执行(它执行 3 次加法 + 1 次增加/跳跃)。
当我展开它超过 2 次(无论多少)时,我的程序运行速度会慢 25%。这可能与对齐有关,但我不清楚是什么。
编辑:这个问题曾经询问为什么 uops 是由 DSB 而不是 MITE 提供的。这现在已经转移到这个问题上。
我试图对一个循环进行基准测试,该循环在我的 Skylake 上添加了 3 个。这个循环应该在一个周期内执行,因为 3 add + 1 increment 与条件跳转融合,一旦融合可以在一个周期内执行。正如预期的那样。
然而,在某些时候,我的 C 编译器试图展开该循环,从而产生更差的性能。我现在试图理解为什么展开循环的性能比非展开循环更差,因为我希望两者具有相同的性能,或者展开循环的速度可能会慢 15%以下。
这是我的 C 代码:
int main() {
int a, b, c, d;
#pragma unroll(2)
for (unsigned long i = 0; i < 2000000000; i++) {
asm volatile("" : "+r" (a), "+r" (b), "+r" (c), "+r" (d));
a = …Run Code Online (Sandbox Code Playgroud) x86 ×9
assembly ×8
intel ×2
performance ×2
32-bit ×1
avx ×1
avx2 ×1
bootloader ×1
c ×1
c++ ×1
intrinsics ×1
nasm ×1
optimization ×1
pack ×1
perf ×1
x86-64 ×1