你删除了Hadi的密码
tmp
最后,gcc对它进行了优化。
看看编译器生成的asm,例如。
on the Godbolt compiler explorer
. 当你试图将这种低级的东西微型化时,你应该一直这样做,特别是当你的计时结果出乎意料的时候。
lfence
clflush [rcx]
lfence
lfence
rdtsc # start of first timed region
lfence
# nothing because tmp=array[0] optimized away.
lfence
mov rcx, rax
sal rdx, 32
or rcx, rdx
rdtsc # end of first timed region
mov edi, OFFSET FLAT:.LC2
lfence
sal rdx, 32
or rax, rdx
sub rax, rcx
mov rsi, rax
mov rbx, rax
xor eax, eax
call printf
您将从中获取有关未使用变量的编译器警告
-Wall
tmp++
不会使
tmp公司
volatile
时间区域外的变量。(或使用内联
asm volatile
perf
提到一些技巧:
https://www.youtube.com/watch?v=nXaxk27zwlk
在GNU C中(至少在gcc和clang中
-O3
),您可以通过强制
(volatile int*)
// int tmp = array[0]; // replace this
(void) *(volatile int*)array; // with this
这个
(void)
避免在空上下文中计算表达式时发出警告,如编写
x;
这看起来像是严格的UB别名,但我的理解是gcc定义了这种行为。Linux内核投射一个指针来添加
不稳定的
its中的限定符
ACCESS_ONCE
宏,所以它用于gcc绝对关心支持的代码库之一。你可以把整个阵列
不稳定的
;它的初始化是否不能自动矢量化并不重要。
# gcc8.2 -O3
lfence
rdtsc
lfence
mov rcx, rax
sal rdx, 32
mov eax, DWORD PTR [rsp] # the load which wasn't there before.
lfence
or rcx, rdx
rdtsc
mov edi, OFFSET FLAT:.LC2
lfence
那你就不必再纠结于
tmp公司
使用,或担心死区消除、CSE或持续传播。实际上
_mm_mfence()
或者Hadi最初的答案中包含了足够的内存限制,使得gcc实际上为cache miss+cache hit情况重新加载,但是它很容易优化掉其中一个重新加载。
注意,这可能会导致asm加载到寄存器中,但从不读取它。当前CPU仍在等待结果(特别是如果
lfence
),但是覆盖结果可能会让一个假设的CPU丢弃负载,而不是等待它。(编译器是否在下一个寄存器之前对它做了其他操作取决于它
lfence公司
,就像
mov
rdtsc
结果在那里。)
对于硬件来说,这是很棘手/不太可能的,因为CPU必须做好异常准备,请参阅
discussion in comments here
What is the latency and throughput of the RDRAND instruction on Ivy Bridge?
),但那可能是个特例。
我自己在Skylake上测试了这个
xor eax,eax
到编译器的asm输出,就在
mov eax, DWORD PTR [rsp]
,以终止缓存未命中加载的结果。但这并不影响时机。
不稳定的
volatile int sink
,以防将来的CPU开始丢弃产生未读结果的UOP。但仍在使用
不稳定的
以确保他们发生在你想要的地方。
,除非你
希望
以空闲时钟速度测量缓存未命中执行时间。看起来您的空计时区域占用了大量的参考周期,所以您的CPU可能被打卡得很慢。
那么,缓存攻击(如熔毁和幽灵)究竟是如何克服这一问题的呢?基本上,他们必须禁用硬件预取器,因为他们试图测量相邻地址,以找到他们是否击中或错过。
作为熔毁或幽灵攻击的一部分,缓存读取端通道通常使用足够大的步幅,以至于硬件预取无法检测访问模式。e、 在单独的页面而不是相邻的行上。最早的谷歌点击之一
meltdown cache read prefetch stride
是
https://medium.com/@mattklein123/meltdown-spectre-explained-6bc8634cc0c2