【发布时间】:2021-12-15 14:24:21
【问题描述】:
我正在使用这本教科书 Randal E. Bryant, David R. O'Hallaron - Computer Systems。 A Programmer's Perspective [3rd ed.] (2016, Pearson),其中有一段我不太理解。
C 代码:
void write_read(long *src, long *dst, long n)
{
long cnt = n;
long val = 0;
while (cnt) {
*dst = val;
val = (*src)+1;
cnt--;
}
}
write_read的内循环:
#src in %rdi, dst in %rsi, val in %rax
.L3:
movq %rax, (%rsi) # Write val to dst
movq (%rdi), %rax # t = *src
addq $1, %rax # val = t+1
subq $1, %rdx # cnt--
jne .L3 # If != 0, goto loop
这是给那些无法接触到 TB 的人的解释:
图 5.35 显示了这个循环代码的数据流表示。该指令
movq %rax,(%rsi)被翻译成两个操作: s_addr 指令计算存储操作的地址,创建一个 存储缓冲区中的条目,以及 设置该条目的地址字段。 s_data 操作为 入口。正如我们将看到的,这两个计算独立执行的事实对程序性能很重要。 这促使分离 参考机中这些操作的功能单元。除了操作之间的数据依赖关系之外 寄存器的写和读,操作符右边的弧线表示 这些操作的一组隐式依赖项。特别是地址 s_addr 操作的计算必须明显在 s_data 操作之前。
在 此外,解码指令
movq (%rdi), %rax生成的加载操作必须检查任何未决存储操作的地址,创建一个数据 它与 s_addr 操作之间的依赖关系。该图显示了一个虚线弧 在 s_data 和加载操作之间。这种依赖是有条件的:如果 两个地址匹配,加载操作必须等到 s_data 已经存入 其结果存入存储缓冲区,但如果两个地址不同,则这两个操作 可以独立进行。
a) 我不太清楚的是为什么在movq %rax,(%rsi) 这行之后需要在调用s_data 之后完成load?我假设在调用s_data 时,%rax 的值存储在%rsi 的地址指向的位置?这是否意味着每次s_data 之后都需要一个load 调用?
b) 它并没有真正显示在图表中,但根据我从书中给出的解释中了解到,movq (%rdi), %rax 这一行需要自己的一组s_addr 和s_data?那么是否准确地说所有movq 调用都需要s_addr 和s_data 调用,然后在调用load 之前检查地址是否匹配?
对这些部分很困惑,如果有人能解释s_addr 和s_data 调用如何与load 一起工作,以及何时需要这些功能,谢谢!!
【问题讨论】:
-
我根本没有得到示例代码——
rsi和dsi没有递增,所以代码只是一次又一次地复制相同的字节。那些movq指令不应该是lodsq和stosq吗? -
不要认为它是
lodsq或stosq,在这个练习中,我们使用movq来工作:) 我们必须将它“分解”为 s_addr/s_data如图所示...@TonyK -
现在您已经添加了
C代码,同样适用。我知道这只是一个例子,但它不必如此毫无意义! (此外,如果cnt为零,则两个 sn-ps 的行为非常不同。) -
@TonyK:谈论memory disambiguation(是否存储转发)看起来像是一个人为的例子。它不需要是一个循环,但是让它成为一个循环可以为多次迭代计时。有趣的事实:现代 CPU 动态预测加载是否来自与早期存储相同的地址,如果存在地址未知的存储(
s_addruop 尚未执行,无论@ 987654360@)。 github.com/travisdowns/uarch-bench/wiki/… 有一些 SKL 实验 -
@TonyK:我假设 asm
do{}while()样式循环只是循环,省略了对cnt == 0的检查以跳过整个循环 that a compiler would have put around it。或者它来自一个构建,其中cnt是一个编译时常量,因此已知为非零。由于问题与此无关,所以没什么大不了的,我认为这个问题不需要更多混乱。
标签: assembly optimization x86 cpu-architecture