在 NVIDIA Fermi 和 Kepler GPU(可能也是 Maxwell)中,L1 缓存线长 128 字节,而 L2 缓存线长 32 字节。不应该反过来吗?我的意思是,L1 小得多,难道它不应该尝试缓存较短的内存段以防止抖动吗?
我在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) 因此还有很多问题,例如https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2018.12.08a.pdf和Preshing的文章如https:/ /preshing.com/20120710/memory-barriers-are-like-source-control-operations/及其整个系列文章就不同的障碍类型提供的排序和可见性保证方面抽象地讨论了内存排序。我的问题是,如何在x86和ARM微体系结构上实现这些障碍和内存排序语义?
对于商店-商店壁垒,好像在x86上,商店缓冲区保持商店的程序顺序并将它们提交到L1D(因此使它们以相同的顺序在全局可见)。如果存储缓冲区未排序,即未按程序顺序维护它们,那么如何实现存储障碍?它只是以这样的方式“标记”存储缓冲区,即在屏障提交之前将存储提交到缓存一致性域,然后在屏障之后提交?还是存储屏障实际上刷新了存储缓冲区并暂停了所有指令,直到刷新完成?可以同时实现吗?
对于负载障碍,如何防止负载重新排序?很难相信x86将按顺序执行所有加载!我假设加载可以乱序执行,但是可以按顺序提交/退出。如果是这样,如果一个cpu在2个不同的位置执行2次加载,那么一个加载如何确保它从T100中得到一个值,而下一个加载在T100上或之后得到它?如果第一个负载未命中高速缓存并正在等待数据,而第二个负载命中并获取其值,该怎么办。当负载1获得其值时,如何确保它获得的值不是来自该负载2的值的较新商店?如果负载可以无序执行,如何检测到违反内存排序的情况?
类似地,如何实现负载存储屏障(在x86的所有负载中都是隐含的)以及如何实现存储负载屏障(例如mfence)?即dmb ld / st和dmb指令在ARM上是如何微体系结构的?每个负载和每个存储区以及mfence指令在x86上如何进行微体系结构,以确保内存排序?
x86 x86-64 cpu-architecture memory-barriers micro-architecture
在现代 Intel 1 x86 上,是否在调度2 时、或完成3时或介于4之间的某个时间点从 RS(保留站)释放负载 uops ?
1我也对 AMD Zen 和续集很感兴趣,所以也可以随意包括在内,但为了使问题易于管理,我将其限制为英特尔。此外,AMD 似乎与英特尔的加载管道略有不同,这可能会使在 AMD 上进行调查成为一项单独的任务。
2这里的调度是指让 RS 执行。
3在这里完成意味着当加载数据返回并准备好满足相关的 uops 时。
4甚至在这两个事件定义的时间范围之外的某个地方,这似乎不太可能,但可能。
我试图了解我的 Haswell 芯片上的 uop 缓存(英特尔文档中的 DSB)的行为。我基于 Intel 优化手册和 Agner pdfs。
我发现了一组案例,其中前端可靠地回退到 MITE 解码器,这取决于代码中的细微变化,这让我感到困惑。
一个例子看起来像这样(在 gnu as 中-msyntax=intel):
mov rax, 100000000
.align 64
1:
// DSB-cacheable nops to
// overflow LSD
.fill 12, 1, 0x90
.align 32
.fill 12, 1, 0x90
.align 32
.fill 12, 1, 0x90
.align 32
.fill 12, 1, 0x90
.align 32
// this first block should fill up way 1 of our uop-cache set
add ecx, ecx
nop
nop
nop
add ecx, ecx
add ecx, …Run Code Online (Sandbox Code Playgroud) x86 assembly cpu-architecture micro-optimization micro-architecture
我们知道,就缓存命中时间而言,直接映射缓存优于集合关联缓存,因为不涉及特定标签的搜索。另一方面,组关联缓存通常比直接映射缓存具有更好的命中率。
我读到,现代处理器试图通过使用一种称为路径预测的技术来结合两者的优点。他们预测给定集合中最有可能发生命中的行,并仅在该行中进行搜索。如果尝试导致未命中,请在该组的所有缓存行中使用正常的组关联搜索。
我想了解这种路径预测是如何工作的。预测硬件/逻辑的延迟如何小于整套的搜索延迟?
caching processor cpu-architecture cpu-cache micro-architecture
假设我们有这个伪代码,但它ptr不在任何 CPU 缓存中:
prefetch_to_L1 ptr
/* 20 cycles */
load ptr
Run Code Online (Sandbox Code Playgroud)
由于ptr在主存中,预取操作的延迟(从预取指令解码到ptr在L1高速缓存中可用)远大于20个周期。正在进行的预取是否会减少负载的延迟?或者预取除非在加载之前完成,否则就没用吗?
天真地(对内存系统如何工作没有太多了解)我可以看到它以两种方式工作:
其中之一是正确的吗?还有我没有想到的第三种选择吗?我对 Skylake 特别感兴趣,但也只是想建立一些一般的直觉。
optimization performance intel cpu-architecture micro-architecture
我正在看一些关于算法的讲座,教授用乘法作为如何改进朴素算法的例子......
它让我意识到乘法并不是那么明显,虽然在我编码时我只是认为它是一个简单的原子操作,乘法需要一个算法来运行,它不像对数字求和那样工作。
所以我想知道,现代桌面处理器实际使用什么算法?我猜他们不依赖对数表,也不用数以千计的总和进行循环......
x86_64 有一条指令movdir64b,据我了解,它是 64 字节(高速缓存行)的非临时副本(好吧,至少是存储)。AArch64 似乎有类似的指令st64b,它执行相同大小的原子存储。但是,官方 ARMv9 文档并不清楚 是否st64b也是非临时存储。
英特尔的指令集参考文档要movdir64b详细得多,但我的研究还不够深入,无法完全理解每种内存类型协议所代表的含义。
据我目前所知, x86_64 指令movntdq大致相当于stnp, ,并且是写组合。由此看来,似乎就像movdir64b一个原子存储中的四个,因此我对 的猜测st64b。
这几乎肯定是对实际情况的过度简化(当然,可能是错误/不准确的),但这是迄今为止可以推断出的。
是否st64b可以将其视为四个stnp指令的原子序列,以这种方式作为缓存行的非临时写入?