x86 获取语义获取和递增?
x86 acquire semantics fetch and increment?
假设我有一个时间戳计数器。
static uint32_t _Atomic timestamp = 0U;
static inline uint32_t get_ts(void) {
return atomic_fetch_add_explicit(×tamp, 1, memory_order_acquire);
}
假设我有一些并发代码,我想通过实验测试和记录内存重新排序。
for (;;) {
uint32_t ts1 = get_ts();
struct result result_a = do_a();
uint32_t ts2 = get_ts();
struct result result_b = do_b();
log(ts1, &result_a);
log(ts2, &result_b);
}
在 c11 内存模型下,do_a
可能会在 get_ts
之后重新排序为 do_b()
。
for (;;) {
uint32_t ts1 = get_ts();
uint32_t ts2 = get_ts();
struct result result_b = do_b();
struct result result_a = do_a();
log(ts1, &result_a);
log(ts2, &result_b);
}
但是,假设编译器不会在 x86 上重新排序 lock xadd
作为所有加载和存储的障碍。因此,x86(但不是编译器)实际上不可能进行这种重新排序,因为 get_ts
调用充当获取释放栅栏。
有没有办法在 x86 上实现真正的获取语义提取和递增?
fetch_add 的 'lock xadd' 实施中有什么不正确的地方?
如果您的意思是您不想要由锁定的 RWM op 提供的完整屏障的更强语义,那么在 x86 上您确实没有任何其他选择。对于加载,普通加载 MOV 指令在这个意义上确实提供了 'true' 获取语义,因为由于存储缓冲,在 MOV 之前按程序顺序执行的存储可能会被其后的其他 CPU 观察到。
是否允许编译器重新排序 do_a
和 do_b
取决于这些函数的作用。
可以对原子进行优化[1], but subject to section 5.1.2.4 of the C11 standard[draft]。 (我相信这是相关的。)
站点 cppreference.com 提供了关于该主题的重新表述的观点,包括 memory_order_acquire
和 memory_order_release
的语义定义。
具有获取语义的加载和具有释放语义的存储可以实现为:
Acquire load Release store
Load #StoreStore
#LoadLoad #LoadStore
#LoadStore Store
其中 # 表示栅栏(内存屏障)。
然而,在 Intel memory ordering white paper 中,明确指出 LoadLoad、StoreStore 和 LoadStore 重新排序 never 在时间 loads/stores.
上执行
因此,release-acquire 语义在 x86 上是免费的。
这不是使用像 lock xadd
这样的锁定(从而序列化)指令的问题;发布-获取语义总是受到尊重——即使是简单的 mov
指令。
请注意,获取语义与获取负载之前执行的存储无关;它订购商店并在 之后加载它。
关于你最后一个问题,有点不清楚。
Is there a way on x86 to have a true acquire semantics fetch and increment?
序列化所有指令的实现具有获取语义。在这方面,x86 不做任何重新排序。
如果你的意思是,有没有办法让原子操作顺序不一致(在 C11 的含义中),那么答案是否定的。
您可以在英特尔手册中找到相关部分,但您也可以在上面链接的白皮书中找到它。它说:
- In a multiprocessor system, locked instructions have a total order.
- Loads and stores are not reordered with locked instructions.
通过要求原子 RMW(读取-修改-写入)操作,您还可以对原子变量强制执行顺序一致的顺序。
假设我有一个时间戳计数器。
static uint32_t _Atomic timestamp = 0U;
static inline uint32_t get_ts(void) {
return atomic_fetch_add_explicit(×tamp, 1, memory_order_acquire);
}
假设我有一些并发代码,我想通过实验测试和记录内存重新排序。
for (;;) {
uint32_t ts1 = get_ts();
struct result result_a = do_a();
uint32_t ts2 = get_ts();
struct result result_b = do_b();
log(ts1, &result_a);
log(ts2, &result_b);
}
在 c11 内存模型下,do_a
可能会在 get_ts
之后重新排序为 do_b()
。
for (;;) {
uint32_t ts1 = get_ts();
uint32_t ts2 = get_ts();
struct result result_b = do_b();
struct result result_a = do_a();
log(ts1, &result_a);
log(ts2, &result_b);
}
但是,假设编译器不会在 x86 上重新排序 lock xadd
作为所有加载和存储的障碍。因此,x86(但不是编译器)实际上不可能进行这种重新排序,因为 get_ts
调用充当获取释放栅栏。
有没有办法在 x86 上实现真正的获取语义提取和递增?
fetch_add 的 'lock xadd' 实施中有什么不正确的地方?
如果您的意思是您不想要由锁定的 RWM op 提供的完整屏障的更强语义,那么在 x86 上您确实没有任何其他选择。对于加载,普通加载 MOV 指令在这个意义上确实提供了 'true' 获取语义,因为由于存储缓冲,在 MOV 之前按程序顺序执行的存储可能会被其后的其他 CPU 观察到。
是否允许编译器重新排序 do_a
和 do_b
取决于这些函数的作用。
可以对原子进行优化[1], but subject to section 5.1.2.4 of the C11 standard[draft]。 (我相信这是相关的。)
站点 cppreference.com 提供了关于该主题的重新表述的观点,包括 memory_order_acquire
和 memory_order_release
的语义定义。
具有获取语义的加载和具有释放语义的存储可以实现为:
Acquire load Release store
Load #StoreStore
#LoadLoad #LoadStore
#LoadStore Store
其中 # 表示栅栏(内存屏障)。
然而,在 Intel memory ordering white paper 中,明确指出 LoadLoad、StoreStore 和 LoadStore 重新排序 never 在时间 loads/stores.
上执行
因此,release-acquire 语义在 x86 上是免费的。
这不是使用像 lock xadd
这样的锁定(从而序列化)指令的问题;发布-获取语义总是受到尊重——即使是简单的 mov
指令。
请注意,获取语义与获取负载之前执行的存储无关;它订购商店并在 之后加载它。
关于你最后一个问题,有点不清楚。
Is there a way on x86 to have a true acquire semantics fetch and increment?
序列化所有指令的实现具有获取语义。在这方面,x86 不做任何重新排序。
如果你的意思是,有没有办法让原子操作顺序不一致(在 C11 的含义中),那么答案是否定的。
您可以在英特尔手册中找到相关部分,但您也可以在上面链接的白皮书中找到它。它说:
- In a multiprocessor system, locked instructions have a total order.
- Loads and stores are not reordered with locked instructions.
通过要求原子 RMW(读取-修改-写入)操作,您还可以对原子变量强制执行顺序一致的顺序。