内存屏障对递增代码的影响是什么?
What is the impact of memory barriers against incrementing code?
考虑以下程序:2 个线程正在迭代同一个函数,该函数包括递增共享计数器变量的值。没有锁保护变量,因此我们谈论的是无锁编程。我们还确保线程将 运行 在不同的 cores/CPUs 上。迭代次数足够大(例如N=100,000)。
操作本身如下,列为伪代码。正如预期的那样,指令之间会有各种延迟,具体取决于 CPU 执行的其他操作。下面的方法只是 运行 对它们进行设置的一种可能方式。
CPU 0 | CPU 1
------------------------------------------
LOAD count |
INC count | LOAD count
| INC count
| STORE count
STORE count |
我们不要只针对内存模型非常强大的 x86 架构。事实上,让我们考虑一个“内存排序敌对”架构(根据 McKenney 的 C.6.1 book)。
此代码的主要问题是——无一例外——最终结果都是错误的。竞争条件将使一个 CPU 计算新的计数器值的频率足够高,而另一个则基于相同的 count
值计算新的计数器值。结果是每个CPU都会向相应的缓存行写回一个count
的增量值,但是是同一个。这与缓存一致性的 MESI 协议并不矛盾,因为每个 CPU 独占获取缓存行并按顺序写入;唯一不幸的是,写入的是相同的计数器值。
不过,我感兴趣的是设置内存屏障的影响。 排除前一段中的问题,内存屏障没有到位(或放置不当)是否会对这个程序的工作方式带来自己的“负面”贡献?
凭直觉考虑存储缓冲区,以及其中的值可能无法“跳过”或“丢失”的事实,最终必须将它们写入缓存行。所以写障碍不会有影响。无效队列和读取障碍是否也没有影响?我的假设正确吗?我错过了什么吗?
你是对的,内存屏障不能创建原子性。它们只命令这个核心自己访问它的 L1d 缓存(例如,在以后存储或加载之前排空存储缓冲区= 完全屏障,或等待较早的加载读取缓存,然后才能执行任何后续加载和存储 = light-weight 屏障)。他们不会将多条指令捆绑到原子 RMW 事务中。
创建原子性。任何另一个核心可以做的事情,你需要 this 核心来保持缓存行在从加载到存储的 MESI Exclusive 或 Modified 状态(). Barriers don't do that, you need special asm instructions like x86 lock add dword [mem], 1
or on many RISC-like machines, an LL/SC retry loop 如果缓存自 load-linked.
以来,该行就不再专用于该核心
内存屏障对 C++ 很重要 std::atomic
因为这也意味着排序(获取、释放或 seq_cst),除非您使用 memory_order_relaxed
,在这种情况下编译器永远不会使用屏障说明。
考虑以下程序:2 个线程正在迭代同一个函数,该函数包括递增共享计数器变量的值。没有锁保护变量,因此我们谈论的是无锁编程。我们还确保线程将 运行 在不同的 cores/CPUs 上。迭代次数足够大(例如N=100,000)。
操作本身如下,列为伪代码。正如预期的那样,指令之间会有各种延迟,具体取决于 CPU 执行的其他操作。下面的方法只是 运行 对它们进行设置的一种可能方式。
CPU 0 | CPU 1
------------------------------------------
LOAD count |
INC count | LOAD count
| INC count
| STORE count
STORE count |
我们不要只针对内存模型非常强大的 x86 架构。事实上,让我们考虑一个“内存排序敌对”架构(根据 McKenney 的 C.6.1 book)。
此代码的主要问题是——无一例外——最终结果都是错误的。竞争条件将使一个 CPU 计算新的计数器值的频率足够高,而另一个则基于相同的 count
值计算新的计数器值。结果是每个CPU都会向相应的缓存行写回一个count
的增量值,但是是同一个。这与缓存一致性的 MESI 协议并不矛盾,因为每个 CPU 独占获取缓存行并按顺序写入;唯一不幸的是,写入的是相同的计数器值。
不过,我感兴趣的是设置内存屏障的影响。 排除前一段中的问题,内存屏障没有到位(或放置不当)是否会对这个程序的工作方式带来自己的“负面”贡献?
凭直觉考虑存储缓冲区,以及其中的值可能无法“跳过”或“丢失”的事实,最终必须将它们写入缓存行。所以写障碍不会有影响。无效队列和读取障碍是否也没有影响?我的假设正确吗?我错过了什么吗?
你是对的,内存屏障不能创建原子性。它们只命令这个核心自己访问它的 L1d 缓存(例如,在以后存储或加载之前排空存储缓冲区= 完全屏障,或等待较早的加载读取缓存,然后才能执行任何后续加载和存储 = light-weight 屏障)。他们不会将多条指令捆绑到原子 RMW 事务中。
创建原子性。任何另一个核心可以做的事情,你需要 this 核心来保持缓存行在从加载到存储的 MESI Exclusive 或 Modified 状态(lock add dword [mem], 1
or on many RISC-like machines, an LL/SC retry loop 如果缓存自 load-linked.
内存屏障对 C++ 很重要 std::atomic
因为这也意味着排序(获取、释放或 seq_cst),除非您使用 memory_order_relaxed
,在这种情况下编译器永远不会使用屏障说明。