在Java 8中,三个内存屏障指令被添加到Unsafe类(源):
/**
* Ensures lack of reordering of loads before the fence
* with loads or stores after the fence.
*/
void loadFence();
/**
* Ensures lack of reordering of stores before the fence
* with loads or stores after the fence.
*/
void storeFence();
/**
* Ensures lack of reordering of loads or stores before the fence
* with loads or stores after the fence.
*/
void fullFence();
Run Code Online (Sandbox Code Playgroud)
如果我们用以下方式定义内存屏障(我认为或多或少容易理解):
考虑X和Y是要重新排序的操作类型/类,
X_YFence()是一个内存屏障指令,它确保在屏障启动之后,在屏障完成任何操作之前,屏障之前的所有类型X操作都已完成.
我们现在可以将障碍名称"映射" Unsafe …
好吧,我已经从SO关于x86处理器围栏阅读下列适量(LFENCE,SFENCE和MFENCE):
和:
而且我必须说实话,我还不能确定何时需要围栏.我试图从删除完全锁定并尝试通过栅栏使用更细粒度的锁定的角度来理解,以最小化延迟延迟.
首先,这是我不明白的两个具体问题:
有时在进行存储时,CPU会写入其存储缓冲区而不是L1缓存.但是,我不了解CPU执行此操作的条款?
CPU2可能希望加载已写入CPU1的存储缓冲区的值.据我了解,问题是CPU2无法在CPU1的存储缓冲区中看到新值.为什么MESI协议不能将刷新存储缓冲区作为其协议的一部分?
更一般地,可以请人试图描述的总体方案,并帮助时解释LFENCE/ MFENCE和SFENCE被需要的指令?
NB阅读本主题的一个问题是,当我只对Intel x86-64架构感兴趣时,"通常"为多CPU架构编写的文章数量.
当我正在阅读有关内存模型,障碍,排序,原子等等时,编译器栅栏的概念经常会出现,但通常情况下,它也会与CPU栅栏配对,正如人们所期望的那样.
但是,偶尔我会读到仅适用于编译器的fence构造.一个例子是C++ 11 std::atomic_signal_fence函数,它在cppreference.com上声明:
std :: atomic_signal_fence等效于std :: atomic_thread_fence,除了没有发出内存排序的CPU指令.仅按顺序指示抑制编译器对指令的重新排序.
我有五个与此主题相关的问题:
正如名称所暗示的那样std::atomic_signal_fence,是一个异步中断(例如一个被内核抢占以执行信号处理程序的线程)唯一一种只有编译器的栅栏才有用的情况?
它的用处是否适用于所有体系结构,包括强烈排序的体系结构x86?
是否可以提供一个特定的示例来演示仅编译器栅栏的用途?
使用时std::atomic_signal_fence,使用acq_rel和seq_cst订购之间有什么区别吗?(我希望它没有任何区别.)
这个问题可能是由第一个问题所覆盖,但我足够的好奇,一下也无妨具体问:是否曾经需要使用围栏与thread_local访问?(如果有的话,我希望只有编译器的围栏atomic_signal_fence才能成为首选工具.)
谢谢.
假设对齐的指针加载和存储在目标平台上自然是原子的,这有什么区别:
// Case 1: Dumb pointer, manual fence
int* ptr;
// ...
std::atomic_thread_fence(std::memory_order_release);
ptr = new int(-4);
Run Code Online (Sandbox Code Playgroud)
这个:
// Case 2: atomic var, automatic fence
std::atomic<int*> ptr;
// ...
ptr.store(new int(-4), std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)
还有这个:
// Case 3: atomic var, manual fence
std::atomic<int*> ptr;
// ...
std::atomic_thread_fence(std::memory_order_release);
ptr.store(new int(-4), std::memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)
我的印象是它们都是等价的,但是Relacy在第一种情况下(仅)检测到数据竞争:
struct test_relacy_behaviour : public rl::test_suite<test_relacy_behaviour, 2>
{
rl::var<std::string*> ptr;
rl::var<int> data;
void before()
{
ptr($) = nullptr;
rl::atomic_thread_fence(rl::memory_order_seq_cst);
}
void thread(unsigned int id)
{
if (id == 0) { …Run Code Online (Sandbox Code Playgroud) 我们知道最终制作字段通常是个好主意,因为我们获得了线程安全性和不变性,这使得代码更易于推理.我很好奇是否有相关的性能成本.
Java内存模型保证了这一点final Field Semantics:
在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值.
这意味着对于像这样的类
class X {
X(int a) {
this.a = a;
}
final int a;
static X instance;
}
Run Code Online (Sandbox Code Playgroud)
每当线程1创建这样的实例
X.instance = new X(43);
while (true) doSomethingEventuallyEvictingCache();
Run Code Online (Sandbox Code Playgroud)
和线程2看到它
while (X.instance == null) {
doSomethingEventuallyEvictingCache();
}
System.out.println(X.instance.a);
Run Code Online (Sandbox Code Playgroud)
它必须打印43.如果没有final修饰符,JIT或CPU可以重新排序存储(第一个存储X.instance然后设置a=43),线程2可以看到默认初始化值并打印0.
当JIT看到final它显然不会重新排序.但它也必须强制CPU遵守命令.是否存在相关的性能损失?
我试图准确理解什么是内存障碍.根据我目前所知,存储器屏障(例如:) mfence用于防止指令从存储器屏障之前到之后和之后重新排序.
这是使用中的内存屏障的示例:
instruction 1
instruction 2
instruction 3
mfence
instruction 4
instruction 5
instruction 6
Run Code Online (Sandbox Code Playgroud)
现在我的问题是:mfence指令只是一个标记,告诉CPU执行指令的顺序是什么?或者它是CPU实际执行的指令,就像它执行其他指令(例如:) mov.
在他的Blog Herb Sutter写道
[...]因为增加智能指针引用计数 通常可以优化为与优化
shared_ptr实现中的普通增量相同- 在生成的代码中只是普通的增量指令,而不是围栏.然而,减量必须是原子减量或等效物,它产生特殊的处理器存储器指令,这些指令本身更昂贵,并且最重要的是在优化周围代码时引起存储器栅栏限制.
该文是关于执行的shared_ptr,我不确定他的评论是否只适用于此或通常是这样.从他的表述我收集它一般.
但是当我想到它时,我只能想到"更加昂贵的减量",当if(counter==0)紧接着 - 这可能就是这种情况shared_ptr.
因此,我想知道原子操作++counter是否(通常)总是快于--counter,或者只是因为它if(--counter==0)...与shared_ptr?一起使用?
Dekker式同步的失败通常通过重新排序指令来解释.即,如果我们写
atomic_int X;
atomic_int Y;
int r1, r2;
static void t1() {
X.store(1, std::memory_order_relaxed)
r1 = Y.load(std::memory_order_relaxed);
}
static void t2() {
Y.store(1, std::memory_order_relaxed)
r2 = X.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
然后负载可以与商店重新排序,导致r1==r2==0.
我期待一个acquire_release围栏来防止这种重新排序:
static void t1() {
X.store(1, std::memory_order_relaxed);
atomic_thread_fence(std::memory_order_acq_rel);
r1 = Y.load(std::memory_order_relaxed);
}
static void t2() {
Y.store(1, std::memory_order_relaxed);
atomic_thread_fence(std::memory_order_acq_rel);
r2 = X.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
负载不能移动到栅栏上方,并且商店不能移动到栅栏下方,因此应该防止不良结果.
但是,实验表明r1==r2==0仍然可以发生.是否有基于重新排序的解释?我推理的缺陷在哪里?
我最近遇到了一些同步问题,这使我成为自旋锁和原子计数器.然后,我正在寻找多一点,这些工作是如何找到的std :: memory_order及记忆力障碍(mfence,lfence和sfence).
所以现在,似乎我应该使用获取/释放螺旋锁并放松计数器.
x86 MFENCE - 内存围栏
x86 LOCK - 断言LOCK#信号
这三个操作(lock = test_and_set,unlock = clear,increment = operator ++ = fetch_add)的默认(seq_cst)内存顺序和获取/释放/放松的机器代码(编辑:见下文)是什么(按此顺序排列三个操作).有什么区别(哪些内存屏障在哪里)和成本(多少CPU周期)?
我只是想知道我的旧代码(没有指定内存顺序= seq_cst使用)真的是多么糟糕,如果我应该创建一些class atomic_counter派生std::atomic但使用轻松的内存排序 (以及在某些地方使用获取/释放而不是互斥锁的好螺旋锁. ..或者使用来自boost库的东西 - 到目前为止我已经避免了提升).
到目前为止,我确实理解自旋锁比自身保护更多(但也有一些共享资源/内存),因此,必须有一些东西可以使一些内存视图对于多个线程/内核(即获取/释放和内存围栏)保持一致).原子计数器只为自己而存在,只需要原子增量(不涉及其他内存,当我读它时我并不真正关心它的价值,它是信息性的,可以是几个循环旧,没问题).有一些LOCK前缀和一些xchg …
memory-fences ×10
atomic ×5
c++ ×4
c++11 ×4
assembly ×2
concurrency ×2
java ×2
performance ×2
x86 ×2
cpu ×1
java-8 ×1
unsafe ×1