正如我们之前对Does it make any sense instruction LFENCE in processors x86/x86_64?的回答所知,我们无法使用SFENCE
代替MFENCE
来实现顺序一致性。
答案显示MFENCE
= SFENCE
+ LFENCE
,即LFENCE
执行某项操作,否则我们无法提供顺序一致性。
LFENCE
无法重新排序:
SFENCE
LFENCE
MOV reg, [addr]
- 收件人 - >
MOV reg, [addr]
SFENCE
LFENCE
例如重新排序MOV [addr], reg
LFENCE
- > {strong>机制 - 存储缓冲区提供了LFENCE
MOV [addr], reg
,它会重新排序存储 - 负载以提高性能,因为LFENCE
不会阻止它。 SFENCE
会停用此机制。
什么机制禁止LFENCE
无法重新排序(x86没有机制 - Invalidate-Queue)?
正在重新排序SFENCE
MOV reg, [addr]
- > MOV reg, [addr]
SFENCE
只能在理论上或现实中实现吗?如果可能,实际上,什么机制,它是如何工作的?
答案 0 :(得分:9)
SFENCE + LFENCE不会阻止StoreLoad重新排序,因此对于顺序一致性来说还不够。只有mfence
(或lock
ed操作或真正的序列化指令(如cpuid
)才能执行此操作。请参阅Jeff Preshing的Memory Reordering Caught in the Act,了解只有完整障碍就足够了。
来自Intel's instruction-set reference manual entry for sfence
:
处理器确保SFENCE之前的每个商店在SFENCE全局可见之后的任何商店之前全局可见。
但
没有针对内存加载或LFENCE指令进行排序。
LFENCE强制先前的指令“在本地完成”(即退出核心的无序部分),但是对于商店或SFENCE而言,这意味着将数据或标记放入内存顺序缓冲区,而不是冲洗它使商店变得全局可见。即 SFENCE“完成”(退出ROB)不包括刷新商店缓冲区。
这就像Preshing在Memory Barriers Are Like Source Control Operations中描述的那样,其中StoreStore障碍并非“即时”。在那篇文章的后面,他解释了为什么#StoreStore + #LoadLoad + #LoadStore屏障不会加起来#StoreLoad屏障。 (x86 LFENCE有一些指令流的额外序列化,但由于它没有刷新存储缓冲区,因此推理仍然存在)。
LFENCE未像cpuid
(which is as strong a memory barrier as mfence
or a lock
ed instruction)那样完全序列化。它只是LoadLoad + LoadStore屏障,加上一些执行序列化的东西,可能作为一个实现细节开始,但现在作为保证,至少在Intel CPU上。它对rdtsc
很有用,并且可以避免分支推测来缓解幽灵。
真正关心的是StoreLoad在商店和负载之间重新排序,而不是在商店和障碍之间重新排序,所以你应该看一个商店的案例,然后是障碍,然后是负载。< / p>
mov [var1], eax
sfence
lfence
mov eax, [var2]
可按此顺序变为全局可见(即提交到L1d缓存):
lfence
mov eax, [var2] ; load stays after LFENCE
mov [var1], eax ; store becomes globally visible before SFENCE
sfence ; can reorder with LFENCE
答案 1 :(得分:3)
一般来说,MFENCE!= SFENCE + LFENCE。例如,使用-DBROKEN
编译时,下面的代码在某些Westmere和Sandy Bridge系统上失败,但似乎可以在Ryzen上运行。事实上,在AMD系统上,SFENCE似乎就足够了。
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
using namespace std;
#define ITERATIONS (10000000)
class minircu {
public:
minircu() : rv_(0), wv_(0) {}
class lock_guard {
minircu& _r;
const std::size_t _id;
public:
lock_guard(minircu& r, std::size_t id) : _r(r), _id(id) { _r.rlock(_id); }
~lock_guard() { _r.runlock(_id); }
};
void synchronize() {
wv_.store(-1, std::memory_order_seq_cst);
while(rv_.load(std::memory_order_relaxed) & wv_.load(std::memory_order_acquire));
}
private:
void rlock(std::size_t id) {
rab_[id].store(1, std::memory_order_relaxed);
#ifndef BROKEN
__asm__ __volatile__ ("mfence;" : : : "memory");
#else
__asm__ __volatile__ ("sfence; lfence;" : : : "memory");
#endif
}
void runlock(std::size_t id) {
rab_[id].store(0, std::memory_order_release);
wab_[id].store(0, std::memory_order_release);
}
union alignas(64) {
std::atomic<uint64_t> rv_;
std::atomic<unsigned char> rab_[8];
};
union alignas(8) {
std::atomic<uint64_t> wv_;
std::atomic<unsigned char> wab_[8];
};
};
minircu r;
std::atomic<int> shared_values[2];
std::atomic<std::atomic<int>*> pvalue(shared_values);
std::atomic<uint64_t> total(0);
void r_thread(std::size_t id) {
uint64_t subtotal = 0;
for(size_t i = 0; i < ITERATIONS; ++i) {
minircu::lock_guard l(r, id);
subtotal += (*pvalue).load(memory_order_acquire);
}
total += subtotal;
}
void wr_thread() {
for (size_t i = 1; i < (ITERATIONS/10); ++i) {
std::atomic<int>* o = pvalue.load(memory_order_relaxed);
std::atomic<int>* p = shared_values + i % 2;
p->store(1, memory_order_release);
pvalue.store(p, memory_order_release);
r.synchronize();
o->store(0, memory_order_relaxed); // should not be visible to readers
}
}
int main(int argc, char* argv[]) {
std::vector<std::thread> vec_thread;
shared_values[0] = shared_values[1] = 1;
std::size_t readers = (argc > 1) ? ::atoi(argv[1]) : 8;
if (readers > 8) {
std::cout << "maximum number of readers is " << 8 << std::endl; return 0;
} else
std::cout << readers << " readers" << std::endl;
vec_thread.emplace_back( [=]() { wr_thread(); } );
for(size_t i = 0; i < readers; ++i)
vec_thread.emplace_back( [=]() { r_thread(i); } );
for(auto &i: vec_thread) i.join();
std::cout << "total = " << total << ", expecting " << readers * ITERATIONS << std::endl;
return 0;
}
答案 2 :(得分:1)
什么机制禁止LFENCE无法重新排序(x86没有机制 - Invalidate-Queue)?
来自英特尔手册,第2-4卷,第3-464页LFENCE
指令的文档:
LFENCE在本地完成所有先前指令之前不会执行,并且在LFENCE完成之前没有后续指令开始执行
是的,LFENCE
指令明确阻止了您的示例重新排序。仅涉及SFENCE
条指令的第二个示例是有效的重新排序,因为SFENCE
对加载操作没有影响。