翻译自:Martin Thompson – Memory Barriers/Fences
在这篇文章里,我将讨论并发编程里最基础的技术–以内存关卡或栅栏著称,那让进程内的内存状态对其他进程可见。
CPU 使用了很多技术去尝试和适应这样的事实:CPU 执行单元的性能已远远超出主内存性能。在我的“Writing Combining”文章,我只是谈及其中一种技术。CPU 使用的用来隐藏内存延迟的最普通技术是管线化指令,然后付出巨大努力和资源去尝试重排序这些管线来最小化缓存不命中的有关拖延。
当一个程序执行的时候,它不在乎,如果重排序后的指令提供了一样的最终结果。例如,在一个循环内,如果循环内没有操作使用循环计算器,循环计数器什 么时候更新是不在乎的。编译器和 CPU 自由地重排序指令来最大化地利用 CPU,直到下一次迭代即将开始时才更新它(循环计数器)。也可能,在一个循环的执行过程中,这个变量可能存储在一个寄存器里,永远不会推到缓存或主内 存,因此,它对其它 CPU 永远不可见。
CPU 核包含多个执行单元。例如,一个现代的Intel CPU 包含6个执行单元,可以做一组数学,条件逻辑和内存操作的组合。每个执行单元可以做这些任务的组合。这些执行单元并行地操作,允许指令并行地执行。如果从 其它 CPU 来观察,这引入了程序顺序的另一层不确定性。
最终,但缓存不命中发生时,现代 CPU 可以根据内存加载的结果做一个假设,然后基于这个假设继续执行直至实际数据的加载完成。
提供“程序顺序”保留了 CPU 和编译器自由地做它们认为可以提升性能的事情。
加载(load)和存储(store)到缓存和主内存是被缓冲和重排序的,使用加载(load),存储(store),和写组合(writing- combining)缓存。这些缓存是关联的队列,允许快速查找。这种查找是必须的,当一个稍后的加载需要读取一个之前存储的、还没有到达缓存的值时。上 图描绘了现代多核 CPU 的简化视图。它显示了执行单元如何使用本地寄存器和缓存来管理内存,与缓存子系统来回传送。
在多线程环境下,需要采用一些技术来让程序结果及时可见。我不会在这篇文章里涉及缓存一致性。仅仅假设一旦内存被推到缓存,然后有一个协议消息将发生,以确保所有共享数据的缓存是一致的。这种使内存对处理器核可见的技术被称为内存关卡或栅栏。
内存关卡提供了两种属性。首先,它们保留了外部可见的程序顺序,通过确保所有的、关卡两侧的指令表现出正确的程序顺序,如果从其他CPU观察。第二,它们使内存可见,通过确保数据传播到缓存子系统。
内存关卡是一个复杂的主题。它们在不同的 CPU 架构上的实现是非常不同的。Intel CPU 有一个关联的强内存模型。本篇将以 x86 CPU 为基础讲解。
存储关卡(store barrier)
存储关卡,在x86 上是”sfence”指令,强迫所有的、在关卡指令之前的 存储指令在关卡以前发生,并且让 store buffers 刷新到发布这个指令的 CPU cache。这将使程序状态对其他 CPU 可见,这样,如果需要它们可以对它做出响应。一个实际的好例子是下面的、简化的、来自Disruptor的类BatchEventProcessor。当sequence被更新后,其他消费者和生产者知道这个消费者的进展,并进行适当的响应。所有在关卡之前对内存的更新现在都可见了。
private volatile long sequence = RingBuffer.INITIAL_CURSOR_VALUE;
// from inside the run() method
T event = null;
long nextSequence = sequence.get() + 1L;
while (running)
{
try
{
// 译注:barrier 会读取其他sequence 的值,所以这里面有个 load barrier 指令。
final long availableSequence = barrier.waitFor(nextSequence);
while (nextSequence <= availableSequence)
{
event = ringBuffer.get(nextSequence);
boolean endOfBatch = nextSequence == availableSequence;
eventHandler.onEvent(event, nextSequence, endOfBatch);
nextSequence++;
}
sequence.set(nextSequence - 1L);
// store barrier 插入到这里 !!!
}
catch (final Exception ex)
{
exceptionHandler.handle(ex, nextSequence, event);
sequence.set(nextSequence);
// store barrier 插入到这里 !!!
nextSequence++;
}
}
加载关卡(load barrier)
加载关卡,在x86 上是”lfence”指令,强迫所有的、加载指令之后的指令在关卡之后发生,然后等待那个 CPU 的 load buffer 排空。这使其它 CPU 暴露出来的程序状态对这个 CPU 可见,在做出更多进展之前。这个的一个好例子是前面引用的 BatchEventProcessor 的 sequence 被其它生产者或消费者读取时,Disruptor 里有等价的指令。
Full Barrier
Full Barrier,在x86 上是”mfence”指令,在 CPU 上是加载和存储关卡的组合。
Java 存储模型(Java Memory Model)
在Java 存储模型里,volatile 字段在写入后插入一个存储关卡,在读取前插入加载关卡。类里面修饰为 final 的字段在它们被初始化后插入一个存储指令,以确这些字段在构造函数完成、有可用引用到这个对象时是可见的。
原子指令和软件锁(Atomic Instructions and Software Locks)
原子指令,如x86里的 “lock …” 指令,是高效的 full barrier,它们锁住存储子系统来执行操作,有受保证的全序关系(total order),即使跨 CPU。软件锁通常使用存储关卡,或原子指令来达到可视性和保留程序顺序。
存储关卡对性能的影响(Performance Impact of Memory Barriers)
存储关卡阻止了 CPU 执行很多隐藏内存延迟的技术,因此有它们有显著的性能开销,必须考虑。为了达到最大性能,最好对问题建模,这样处理器可以做工作单元,然后让所有必须的存 储关卡在工作单元的边界上发生。采用这种方法允许处理器不受限制地优化工作单元。把必须的存储关卡分组是有益的,那样,在第一个之后的 buffer 刷新的开销会小点,因为没有工作需要进行重新填充它。
译注:关于Disruptor可以看我之前的这篇文章:http://coderbee.net/index.php/open-source/20130812/400
相关推荐
文章深入探讨了内存屏障(Memory Barriers)的概念及其在现代多处理器系统中的作用,并解释了为什么CPU设计者需要将内存屏障强加给SMP(对称多处理器)软件设计师。 #### 并发同步与内存屏障 内存屏障是用于确保...
内存屏障是一种在多处理器系统中被广泛使用的同步机制,它确保了内存操作的顺序性,对于保证多核处理器环境下软件的正确运行至关重要。为了更好地理解内存屏障的作用和它在硬件层面的表现,我们有必要先了解CPU缓存...
以前我们说过在一些简单的例子中,比如为一个字段赋值或递增该字段,我们需要对...Memory Barriers and Volatility (内存栅栏和易失字段 )考虑下下面的代码: 代码如下:int _answer; bool _complete; void A() {
内存屏障是计算机科学中的一个概念,主要用于多核处理器或多处理器环境中,确保数据的可见性和一致性。在Linux内核中,内存屏障是用来控制指令执行顺序的机制,以保证不同处理器或不同硬件间的一致性操作。内存屏障...
记忆屏障(Memory Barrier),有时也被称为内存屏障或栅栏(Fence),是一种同步机制,用于控制系统中不同处理器或处理器核心之间的内存访问顺序。在多线程或多处理器的环境中,为了优化性能,编译器和处理器都可能...
内存屏障(Memory Barriers)是现代计算机系统中一个关键概念,尤其在多处理器系统(SMP)环境下,对于确保数据一致性至关重要。本文将深入探讨内存屏障的基本原理、作用机制以及它们在硬件层面如何支持软件设计。 ...
为了解决这个问题,Linux社区在过去依赖于文档《Documentation/memory-barriers.txt》以及一些专家的代码审查,如Paul Mckenney、Peter Zijlstra和Will Deacon等人。随着时间的推移,尤其是在2014年到2015年期间,...
Linux内核通过一系列机制确保了内存操作的顺序性和一致性,其中最重要的机制之一便是内存屏障(memory barriers)。 首先,介绍Linux系统内存一致性模型。在多核系统中,每个CPU核心都有自己的缓存,这些缓存之间的...
为了检测内存错误,Linux内核还引入了内存屏障(Memory Barriers)和锁机制,以保证内存操作的顺序性和一致性。此外,还有内存调试工具如KMemleak,它可以检测到未释放的内存,帮助开发者定位内存泄漏问题。 另外,...
例如,如果没有适当的内存屏障(memory barriers),编译器可能会重新排列指令顺序,导致一个线程看到的数据与预期不符。 #### 四、C++09内存模型的特点 为了应对这些问题,C++09引入了一个全新的内存模型,该模型...
Java内存模型是Java虚拟机规范中定义的一部分,它规定了Java程序中变量的读写行为,以及线程之间的交互规则。理解Java内存模型对于编写正确、高效的多线程程序至关重要。在Java 5之前,Java内存模型的描述比较模糊,...
《使用OpenMP:便携式共享内存并行编程》是一本深入探讨OpenMP技术的书籍,出版于2007年。OpenMP是并行计算领域广泛应用的一种接口标准,它为程序员提供了一种简单的方式来编写多线程程序,使得代码在支持共享内存...
例如,使用原子操作(atomic operations)或内存屏障(memory barriers)可以避免数据竞争(data races)。 9. **资源管理**:共享内存段在创建后需要妥善管理,包括清理不再使用的内存段(使用`shmctl`的IPC_RMID...
CUDA提供了不同级别的同步机制,包括内存栅栏(Memory Barriers)和原子操作(Atomic Operations)。试题可能会考察如何使用这些同步工具来解决数据竞争和确保内存一致性的问题。 5. 并行算法设计:并行算法设计是...
这包括CUDA编程语言的基本元素,如内核函数(Kernel Functions)、全局内存(Global Memory)、共享内存(Shared Memory)、常量内存(Constant Memory)和纹理内存(Texture Memory)等。理解这些内存层次和它们的...
### 环境噪声屏障:声学与视觉设计指南 #### 知识点一:环境噪声屏障概述 - **定义**:环境噪声屏障是指用于减少交通或其他来源产生的噪声对周围环境影响的一种结构或设施。 - **重要性**:随着城市化进程的加快,...
首先,【标题】中提到的“Breaking the wireless barriers to mobilize 5G NR mmWave.pdf”表明这份文件可能涉及如何通过解决无线通信中的难题来推进5G NR(New Radio,新无线)的毫米波技术。毫米波是5G技术的关键...