OpenSSL中的功能 BN_consttime_swap
是一件美丽的事情.在此片段中,condition
已计算为0
或(BN_ULONG)-1
:
#define BN_CONSTTIME_SWAP(ind) \
do { \
t = (a->d[ind] ^ b->d[ind]) & condition; \
a->d[ind] ^= t; \
b->d[ind] ^= t; \
} while (0)
…
BN_CONSTTIME_SWAP(9);
…
BN_CONSTTIME_SWAP(8);
…
BN_CONSTTIME_SWAP(7);
Run Code Online (Sandbox Code Playgroud)
目的是为了确保更高级别的bignum操作需要恒定的时间,这个功能要么交换两个bignum,要么在恒定时间内将它们留在原位.当它离开它们时,它实际上读取每个bignum的每个单词,计算一个与旧单词相同的新单词,并将该结果写回原始位置.
目的是这将花费相同的时间,好像有效地交换了bignums.
在这个问题中,我假设一个现代的,广泛的架构,如Agner Fog在他的优化手册中描述的那些.还假设C代码直接转换为汇编(没有C编译器撤消程序员的努力).
我试图理解上面的构造是否表征为"尽力而为"的恒定时间执行,或者是完美的恒定时间执行.
特别是,我担心a
在BN_consttime_swap
调用函数时bignum 已经在L1数据缓存中的情况,并且函数返回后的代码立即开始在bignum上工作a
.在现代处理器上,足够的指令可以同时在飞行中,以便在a
使用bignum时技术上不会完成复制.在调用之后允许指令BN_consttime_swap
工作的机制a
是存储器依赖性推测.让我们假设为了论证而进行天真的记忆依赖性推测.
这个问题似乎归结为:
当处理器最终检测到BN_consttime_swap
从内存读取后的代码,与推测相反,已经写入函数内部时,一旦检测到地址已被写入,它是否取消推测执行,或者它是否允许自己当它检测到已写入的值与已存在的值相同时保留它?
在第一种情况下,BN_consttime_swap
看起来它实现了完美的恒定时间.在第二种情况下,它只是尽力而为的恒定时间:如果没有交换bignums,那么在调用之后执行的代码BN_consttime_swap
将比它们被交换时快得多.
即使在第二种情况下,这看起来似乎可以在可预见的未来固定(只要处理器保持足够天真),对于两个bignums中的每一个的每个单词,写出不同于两个可能的最终值的值.在再次写入旧值或新值之前的值.该volatile
类型修饰符可能需要在某一时刻参与,以防止一个普通的编译器过度优化的序列,但它仍然听起来可能. …
当发生高速缓存未命中时,CPU从主存储器中取出整个高速缓存行到高速缓存层次结构中.(通常在x86_64上为64个字节)
这是通过数据总线完成的,在现代64位系统上只有8字节宽.(因为字长是8字节)
编辑: "数据总线"表示在此上下文中CPU芯片和DRAM模块之间的总线.该数据总线宽度不一定与字大小相关.
根据策略,首先获取实际请求的地址,然后按顺序获取高速缓存行的其余部分.
如果有一个64字节宽的总线,它会更快,这将允许一次获取整个高速缓存行.(这将是字长的八倍)
也许可能有两种不同的数据总线宽度,一种用于标准高速缓存线提取,另一种用于外部硬件(DMA),仅适用于字大小存储器访问.
限制数据总线大小的限制是什么?
memory caching cpu-architecture cpu-cache micro-architecture
一般理解为每个store分配一个store buffer entry,这个store buffer entry保存了store数据和物理地址1。
在存储跨越 4096 字节页面边界的情况下,可能需要两个不同的转换,每个页面一个,因此可能需要存储两个不同的物理地址。这是否意味着跨页存储需要 2 个存储缓冲区条目?如果是这样,它是否也适用于跨线商店?
1 ...也许还有一些/全部虚拟地址来帮助存储转发。
x86 intel cpu-architecture micro-optimization micro-architecture
Linux 定义了一个BX
在支持它的 CPU上使用的汇编宏,这让我怀疑有一些性能原因。
这个答案和Cortex-A7 MPCore 技术参考手册也指出它有助于分支预测。
然而,我的基准测试工作未能发现与 ARM1176、Cortex-A17、Cortex-A72 和 Neoverse-N1 cpu 的性能差异。
有没有因此任何理由,更喜欢BX
过MOV pc,
上了MMU的CPU,并实现了32位ARM指令集,比Thumb代码交互等?
编辑添加基准代码,全部对齐到 64 字节:
执行无用的计算lr
并使用返回BX
:
div_bx
mov r9, #2
mul lr, r9, lr
udiv lr, lr, r9
mul lr, r9, lr
udiv lr, lr, r9
bx lr
Run Code Online (Sandbox Code Playgroud)
在另一个寄存器上执行无用的计算并使用BX
以下方法返回:
div_bx2
mov r9, #2
mul r3, r9, lr
udiv r3, r3, r9
mul r3, r9, r3
udiv r3, r3, r9
bx lr …
Run Code Online (Sandbox Code Playgroud) assembly arm cpu-architecture branch-prediction micro-architecture
首先,我在 IvyBridge 上进行了以下设置,我将在注释位置插入测量有效负载代码。前 8 个字节buf
存储buf
自身的地址,我用它来创建循环携带依赖:
section .bss
align 64
buf: resb 64
section .text
global _start
_start:
mov rcx, 1000000000
mov qword [buf], buf
mov rax, buf
loop:
; I will insert payload here
; as is described below
dec rcx
jne loop
xor rdi, rdi
mov rax, 60
syscall
Run Code Online (Sandbox Code Playgroud)
我插入到有效载荷位置:
mov qword [rax+8], 8
mov rax, [rax]
Run Code Online (Sandbox Code Playgroud)
perf
显示循环为 5.4c/iter。有点理解,因为L1d延迟是4个周期。
我颠倒了这两条指令的顺序:
mov rax, [rax]
mov qword [rax+8], 8
Run Code Online (Sandbox Code Playgroud)
结果突然变成9c/iter。我不明白为什么。因为下一次迭代的第一条指令不依赖于当前迭代的第二条指令,所以这个设置应该和 case 1 没有区别。
我也用IACA工具对这两种情况进行静态分析,但是该工具不可靠,因为两种情况预测的结果都是5.71c/iter,与实验相矛盾。 …
x86 assembly micro-optimization microbenchmark micro-architecture
英特尔手动优化(2019 年 9 月修订版)显示了用于 Ice Lake 微架构的 48 KiB 8 路关联 L1 数据缓存。
这让我感到困惑,因为:
总而言之,缓存的处理成本似乎更高,但延迟仅略有增加(如果确实如此,则取决于英特尔对该数字的确切含义)。
有一点创造力,我仍然可以想象一种快速索引 96 组的方法,但第二点对我来说似乎是一个重要的突破性变化。
我错过了什么?
我正在阅读 MDS 攻击论文RIDL:Rogue In-Flight Data Load。他们讨论了 Line Fill Buffer 如何导致数据泄漏。有关于 RIDL 漏洞和负载的“重放”问题讨论了漏洞利用的微架构细节。
阅读该问题后,我不清楚的一件事是,如果我们已经有了存储缓冲区,为什么还需要行填充缓冲区。
John McCalpin 在WC-buffer 与LFB 有什么关系?中讨论了存储缓冲区和行填充缓冲区是如何连接的?在英特尔论坛上,但这并没有真正让我更清楚。
对于存储到 WB 空间,存储数据将保留在存储缓冲区中,直到存储退出之后。退役后,数据可以写入 L1 数据缓存(如果该行存在且具有写入权限),否则会为存储未命中分配一个 LFB。LFB 最终会收到缓存行的“当前”副本,以便它可以安装在 L1 数据缓存中,并且可以将存储数据写入缓存。合并、缓冲、排序和“捷径”的细节尚不清楚......与上述合理一致的一种解释是 LFB 用作缓存行大小的缓冲区,其中存储数据在发送到L1 数据缓存。至少我认为这是有道理的,但我可能忘记了一些事情......
我最近才开始阅读乱序执行,所以请原谅我的无知。这是我关于商店如何通过商店缓冲区和行填充缓冲区的想法。
Intel 硬件预取器 Intel 网站显示有四种硬件预取器。由第 3 位控制的预取器是 L1 步幅预取器。我正在运行一个测试代码来测试 stride 预取器的触发条件是什么。我按照以下步骤运行代码(将 MSR0x1a4 设置为 0x7,这意味着仅启用基于 L1 IP 的 strider 预取器):
repeat following for 10000 times:
flush
training phase: access line 0 3 6 9
sleep for near 1000 cycles
measure phase: measure line 12
Run Code Online (Sandbox Code Playgroud)
我希望看到第 12 行被预取到缓存中。但是我只能看到缓存中的第 0 3 6 9 行被命中。即使我更改了步幅或访问模式的长度,也无法观察到步幅预取活动。所以我想知道是否有人在英特尔处理器中看到过预取活动,或者有一些我没有注意到的特殊触发条件?
对这个案例有兴趣的可以试一下测试代码。运行sudo ./run.sh
就可以了。我的机器上的结果显示,第 12 行的访问时间大多大于 180 个周期。我认为时间测量代码没有问题,因为如果我将测量的行从缓存行 12 更改为缓存行 6(只需在 test.c,第 103 行更改),那么访问时间主要是 25 个周期。
有没有办法让gcc输出可用的-march = arch选项?我遇到构建错误(尝试过-march=x86_64
),我不知道我的选择是什么。
我使用的编译器是gcc似乎不喜欢的专有包装-march=skylake
。标志应该是相同的,因此我假设要发送给gcc的所有选项以转储体系结构对于此包装器来说都是相同的。
我设法使gcc使用伪造的参数出错,并转储了一个列表,但是现在我正在通过包装器,所以我看不到它。
如何让gcc告诉我它支持什么?
在题为“数据操作数独立时序指令集架构 (ISA) 指南”的文档中,英特尔推出了一种新的IA32_UARCH_MISC_CTL
MSR,其中切换位 0 可启用“数据操作数独立时序模式”(DOITM)。此 MSR 可在代号为 Ice Lake 的 Intel Core、代号为 Gracemont 的 Atom 以及较新的 CPU(即撰写本文时的 Alder Lake)上使用。Ice Lake 和 Gracemont 之前的处理器的行为就像始终启用 DOIT 模式一样。
在 Intel Core CPU 上,不涉及复杂微代码的整数指令通常被理解为对于数据操作数具有固定延迟,但对于地址操作数则不然(自 Ice Lake 以来,整数除法似乎以固定延迟运行,并且在此之前进行了微编码[uops .info])。当涉及次正规时,浮点指令可能会有所不同。这看起来很自然,因为 CPU 后端需要提前计算结果的可用性。
在 DOIT 文档中,英特尔似乎表示他们正在设想进一步的 CPU 优化,其中某些指令可能具有更短的延迟,具体取决于其数据操作数。除了令人困惑的短语外,优化的确切性质并未公开:
例如,启用数据操作数独立计时可能会禁用数据相关预取
这很难解释为适用于数据操作数,但不适用于地址操作数。
根据数据操作数的不同,可以有意义地具有不同延迟的指令的一个具体示例是指令IMUL
:当乘以在寄存器重命名时已知为零的操作数时(因为它之前在重命名阶段按照习惯用法被归零xor same, same
),则结果也可以在重命名阶段解析为零,以实现零执行延迟而不是三或四个周期。类似的技术可以应用于许多基本的单周期 ALU 操作(例如,将重命名为 0 的操作数的ADD
/ OR
/的结果解析为第二个操作数)。XOR
在 DOIT 模式下,某些(但不是全部)指令保证相对于数据操作数具有固定延迟,就像在 Ice Lake 之前的 CPU 上一样。标题为“数据操作数独立时序指令”的随附文档中列举了此类指令。有点令人困惑的是,该列表包括类似LDDQU
和的指令POP
,但没有任何数据操作数。
切换 DOIT …