Skip to main content
  1. Posts/

内存屏障(Memory Barriers)

·5 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

起因是最近在看levelDB源码,其中port里的atomic_pointer.h文件用到了内存屏障。。

于是来学习一下。。

粗略得说下我自己的理解。

代码的顺序并不和执行的顺序完全对应,出于对效率的追求,cpu和编译器会对一些顺序指令重排,以期得到最大的执行效率。

比如下面这段代码:

1// example 2
2    // void *ptr, v, _store;
3    v = ptr;
4    _store = v;
5    somefunc();
6    v = _store;

v的值是没有改变的,那么编译器可能会认为_store = v; v = _store; 是多余的,就直接把这一段给“优化”掉了。这段代码在单线程中确实是多余的,但是在多线程环境下,可能在somefunc()被调用的时候,另一个线程把v的值给改变了,而这种情况是编译器无法发现的。因此,为了避免这种情况。。。内存屏障登场!

摘自维基百科:

内存屏障,也称内存栅栏内存栅障屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。

大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。

语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。

在多线程环境里需要使用某种技术来使程序结果尽快可见。。请先假定一个事实:一旦内存数据被推送到缓存,就会有消息协议来确保所有的缓存会对所有的共享数据同步并保持一致。这个使内存数据对CPU核可见的技术被称为内存屏障或内存栅栏

再看一个例子

1// get start time
2for (int i = 0; i != 100000; i++) {
3    MemoryBarrier()
4}
5// get end time

这段代码,是想知道for循环空转100000次的耗时,这里就需要加入一个MemoryBarrier,如果不加,那么编译器可能就会直接把这个无意义的for循环直接优化掉了。

除了编译器,cpu由于指令流水线或者超流水线等计数,也可能导致出现乱序执行的情况。

内存屏障提供了两个功能。首先,它们通过确保从另一个CPU来看屏障的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;其次它们可以实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。

不过内存平展由于阻碍了cpu和编译器的部分优化。。。因此对性能的影响是不忽略的。

为了达到最佳性能,最好是把要解决的问题模块化,这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障。采用这个方法可以让处理器不受限的执行一个任务单元。合理的内存屏障组合还有一个好处是:缓冲区在第一次被刷后开销会减少,因为再填充改缓冲区不需要额外工作了。

内存屏障的实现不同平台差别很大。。。因为我们可以看到atomic_pointer.h文件中 一堆和平台相关的条件编译…

  1// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style license that can be
  3// found in the LICENSE file. See the AUTHORS file for names of contributors.
  4
  5// AtomicPointer provides storage for a lock-free pointer.
  6// Platform-dependent implementation of AtomicPointer:
  7// - If the platform provides a cheap barrier, we use it with raw pointers
  8// - If <atomic> is present (on newer versions of gcc, it is), we use
  9//   a <atomic>-based AtomicPointer.  However we prefer the memory
 10//   barrier based version, because at least on a gcc 4.4 32-bit build
 11//   on linux, we have encountered a buggy <atomic> implementation.
 12//   Also, some <atomic> implementations are much slower than a memory-barrier
 13//   based implementation (~16ns for <atomic> based acquire-load vs. ~1ns for
 14//   a barrier based acquire-load).
 15// This code is based on atomicops-internals-* in Google's perftools:
 16// http://code.google.com/p/google-perftools/source/browse/#svntrunksrcbase
 17
 18#ifndef PORT_ATOMIC_POINTER_H_
 19#define PORT_ATOMIC_POINTER_H_
 20
 21#include <stdint.h>
 22#ifdef LEVELDB_ATOMIC_PRESENT
 23#include <atomic>
 24#endif
 25#ifdef OS_WIN
 26#include <windows.h>
 27#endif
 28#ifdef OS_MACOSX
 29#include <libkern/OSAtomic.h>
 30#endif
 31
 32#if defined(_M_X64) || defined(__x86_64__)
 33#define ARCH_CPU_X86_FAMILY 1
 34#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
 35#define ARCH_CPU_X86_FAMILY 1
 36#elif defined(__ARMEL__)
 37#define ARCH_CPU_ARM_FAMILY 1
 38#elif defined(__aarch64__)
 39#define ARCH_CPU_ARM64_FAMILY 1
 40#elif defined(__ppc__) || defined(__powerpc__) || defined(__powerpc64__)
 41#define ARCH_CPU_PPC_FAMILY 1
 42#elif defined(__mips__)
 43#define ARCH_CPU_MIPS_FAMILY 1
 44#endif
 45
 46namespace leveldb {
 47namespace port {
 48
 49// Define MemoryBarrier() if available
 50// Windows on x86
 51#if defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY)
 52// windows.h already provides a MemoryBarrier(void) macro
 53// http://msdn.microsoft.com/en-us/library/ms684208(v=vs.85).aspx
 54#define LEVELDB_HAVE_MEMORY_BARRIER
 55
 56// Mac OS
 57#elif defined(OS_MACOSX)
 58inline void MemoryBarrier() {
 59  OSMemoryBarrier();
 60}
 61#define LEVELDB_HAVE_MEMORY_BARRIER
 62
 63// Gcc on x86
 64#elif defined(ARCH_CPU_X86_FAMILY) && defined(__GNUC__)
 65inline void MemoryBarrier() {
 66  // See http://gcc.gnu.org/ml/gcc/2003-04/msg01180.html for a discussion on
 67  // this idiom. Also see http://en.wikipedia.org/wiki/Memory_ordering.
 68  __asm__ __volatile__("" : : : "memory");
 69}
 70#define LEVELDB_HAVE_MEMORY_BARRIER
 71
 72// Sun Studio
 73#elif defined(ARCH_CPU_X86_FAMILY) && defined(__SUNPRO_CC)
 74inline void MemoryBarrier() {
 75  // See http://gcc.gnu.org/ml/gcc/2003-04/msg01180.html for a discussion on
 76  // this idiom. Also see http://en.wikipedia.org/wiki/Memory_ordering.
 77  asm volatile("" : : : "memory");
 78}
 79#define LEVELDB_HAVE_MEMORY_BARRIER
 80
 81// ARM Linux
 82#elif defined(ARCH_CPU_ARM_FAMILY) && defined(__linux__)
 83typedef void (*LinuxKernelMemoryBarrierFunc)(void);
 84// The Linux ARM kernel provides a highly optimized device-specific memory
 85// barrier function at a fixed memory address that is mapped in every
 86// user-level process.
 87//
 88// This beats using CPU-specific instructions which are, on single-core
 89// devices, un-necessary and very costly (e.g. ARMv7-A "dmb" takes more
 90// than 180ns on a Cortex-A8 like the one on a Nexus One). Benchmarking
 91// shows that the extra function call cost is completely negligible on
 92// multi-core devices.
 93//
 94inline void MemoryBarrier() {
 95  (*(LinuxKernelMemoryBarrierFunc)0xffff0fa0)();
 96}
 97#define LEVELDB_HAVE_MEMORY_BARRIER
 98
 99// ARM64
100#elif defined(ARCH_CPU_ARM64_FAMILY)
101inline void MemoryBarrier() {
102  asm volatile("dmb sy" : : : "memory");
103}
104#define LEVELDB_HAVE_MEMORY_BARRIER
105
106// PPC
107#elif defined(ARCH_CPU_PPC_FAMILY) && defined(__GNUC__)
108inline void MemoryBarrier() {
109  // TODO for some powerpc expert: is there a cheaper suitable variant?
110  // Perhaps by having separate barriers for acquire and release ops.
111  asm volatile("sync" : : : "memory");
112}
113#define LEVELDB_HAVE_MEMORY_BARRIER
114
115// MIPS
116#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(__GNUC__)
117inline void MemoryBarrier() {
118  __asm__ __volatile__("sync" : : : "memory");
119}
120#define LEVELDB_HAVE_MEMORY_BARRIER
121
122#endif
123
124// AtomicPointer built using platform-specific MemoryBarrier()
125#if defined(LEVELDB_HAVE_MEMORY_BARRIER)
126class AtomicPointer {
127 private:
128  void* rep_;
129 public:
130  AtomicPointer() { }
131  explicit AtomicPointer(void* p) : rep_(p) {}
132  inline void* NoBarrier_Load() const { return rep_; }
133  inline void NoBarrier_Store(void* v) { rep_ = v; }
134  inline void* Acquire_Load() const {
135    void* result = rep_;
136    MemoryBarrier();
137    return result;
138  }
139  inline void Release_Store(void* v) {
140    MemoryBarrier();
141    rep_ = v;
142  }
143};
144
145// AtomicPointer based on <cstdatomic>
146#elif defined(LEVELDB_ATOMIC_PRESENT)
147class AtomicPointer {
148 private:
149  std::atomic<void*> rep_;
150 public:
151  AtomicPointer() { }
152  explicit AtomicPointer(void* v) : rep_(v) { }
153  inline void* Acquire_Load() const {
154    return rep_.load(std::memory_order_acquire);
155  }
156  inline void Release_Store(void* v) {
157    rep_.store(v, std::memory_order_release);
158  }
159  inline void* NoBarrier_Load() const {
160    return rep_.load(std::memory_order_relaxed);
161  }
162  inline void NoBarrier_Store(void* v) {
163    rep_.store(v, std::memory_order_relaxed);
164  }
165};
166
167// Atomic pointer based on sparc memory barriers
168#elif defined(__sparcv9) && defined(__GNUC__)
169class AtomicPointer {
170 private:
171  void* rep_;
172 public:
173  AtomicPointer() { }
174  explicit AtomicPointer(void* v) : rep_(v) { }
175  inline void* Acquire_Load() const {
176    void* val;
177    __asm__ __volatile__ (
178        "ldx [%[rep_]], %[val] \n\t"
179         "membar #LoadLoad|#LoadStore \n\t"
180        : [val] "=r" (val)
181        : [rep_] "r" (&rep_)
182        : "memory");
183    return val;
184  }
185  inline void Release_Store(void* v) {
186    __asm__ __volatile__ (
187        "membar #LoadStore|#StoreStore \n\t"
188        "stx %[v], [%[rep_]] \n\t"
189        :
190        : [rep_] "r" (&rep_), [v] "r" (v)
191        : "memory");
192  }
193  inline void* NoBarrier_Load() const { return rep_; }
194  inline void NoBarrier_Store(void* v) { rep_ = v; }
195};
196
197// Atomic pointer based on ia64 acq/rel
198#elif defined(__ia64) && defined(__GNUC__)
199class AtomicPointer {
200 private:
201  void* rep_;
202 public:
203  AtomicPointer() { }
204  explicit AtomicPointer(void* v) : rep_(v) { }
205  inline void* Acquire_Load() const {
206    void* val    ;
207    __asm__ __volatile__ (
208        "ld8.acq %[val] = [%[rep_]] \n\t"
209        : [val] "=r" (val)
210        : [rep_] "r" (&rep_)
211        : "memory"
212        );
213    return val;
214  }
215  inline void Release_Store(void* v) {
216    __asm__ __volatile__ (
217        "st8.rel [%[rep_]] = %[v]  \n\t"
218        :
219        : [rep_] "r" (&rep_), [v] "r" (v)
220        : "memory"
221        );
222  }
223  inline void* NoBarrier_Load() const { return rep_; }
224  inline void NoBarrier_Store(void* v) { rep_ = v; }
225};
226
227// We have neither MemoryBarrier(), nor <atomic>
228#else
229#error Please implement AtomicPointer for this platform.
230
231#endif
232
233#undef LEVELDB_HAVE_MEMORY_BARRIER
234#undef ARCH_CPU_X86_FAMILY
235#undef ARCH_CPU_ARM_FAMILY
236#undef ARCH_CPU_ARM64_FAMILY
237#undef ARCH_CPU_PPC_FAMILY
238
239}  // namespace port
240}  // namespace leveldb
241
242#endif  // PORT_ATOMIC_POINTER_H_

参考资料:

内存屏障_维基百科

内存屏障_并发编程网

LINUX内核之内存屏障

Related

AWK 初探

·6 mins
参考资料: awk_维基百科 awk简明教程 awk是一门比较古老但是很好用的文本处理工具(语言?)