对于 x86_64、arm 或其他 arch,11 是 ISO c++ 下的有效输出吗?
Is 11 a valid output under ISO c++ for x86_64,arm or other arch?
这个问题是基于
我同意给出的答案。
在 x86 上 00 永远不会发生 因为 a.fetch_add 有一个锁 prefix/full 障碍并且负载不能在 fetch_add 以上重新排序但是在其他架构上 arm/mips它可以打印00。我有两个关于 x86 和 arm 上的存储缓冲区的后续问题。
我的电脑(core i3 x86_64)上从来没有得到 11,即 11 是一个有效的输出
iso c++ 中的 x86,所以我 缺少某些东西 吗? @Daniel Langr 证明 11 是 x86 上的有效输出。
现在x86_64有优势fetch_add作为全屏障
对于 arm64,由于 cpu 指令 reordering.
有时输出可能为 00
对于 arm64 或其他一些 arch,如果 不重新排序,输出是否可以为 00?。我的问题
是基于此。函数 foo 的存储缓冲区值
a.fetch_add(1) 不可见 bar 的 a.load() b.fetch_add(1) 是
对于 foo 的 b.load() 不可见。因此我们得到 00 而无需重新排序。这会在不同架构的 ISO C++ 下发生吗?
// g++ -O2 -pthread axbx.cpp ; while [ true ]; do ./a.out | grep "00" ; done
#include<cstdio>
#include<thread>
#include<atomic>
using namespace std;
atomic<int> a,b;
int reta,retb;
void foo(){
a.fetch_add(1,memory_order_relaxed); //add to a is stored in store buffer of cpu0
//a.store(1,memory_order_relaxed);
retb=b.load(memory_order_relaxed);
}
void bar(){
b.fetch_add(1,memory_order_relaxed); //add to b is stored in store buffer of cpu1
//b.store(1,memory_order_relaxed);
reta=a.load(memory_order_relaxed);
}
int main(){
thread t[2]{ thread(foo),thread(bar) };
t[0].join(); t[1].join();
printf("%d%d\n",reta,retb);
return 0;
}
是的,ISO C++ 允许这样做;正如 Daniel 指出的那样,一种简单的方法是在 RMW 之后,加载之前放置一些慢速的东西,这样它们就不会执行,直到两个线程都有机会增加。这应该是显而易见的,因为它不需要任何 运行 时间重新排序发生,只是程序顺序的简单交错。 (所以 ISO C++ 允许 11
即使 seq_cst。)即你可以通过单独单步执行每个线程来练习这个。
您想知道如何在没有延迟循环的情况下在 x86 上创建实际演示吗?
尝试将原子变量放在单独的缓存行中,以便两个不同的内核可以并行写入它们。
alignas(64) std::atomic<int> a, b; // the alignas applies to each separately
由于它们在同一个缓存行中,就像默认情况下可能发生的那样,赢得了它们所在的缓存行所有权的核心将能够在增量的全屏障部分已完成。完成 RMW 意味着该内核的 L1d 缓存中的缓存行已经很热。 (核心只能在通过 MESI 获得独占所有权后修改缓存行,这也将使其对读取有效。)
所以一个线程的两个操作极有可能在另一个线程的任何一个操作之前发生。 (在 x86 asm 中,每个操作都具有与其 seq_cst 等价物相同的 asm,因此我们可以有效地讨论全局操作顺序而不会丢失任何内容。)
可能唯一能阻止这种情况发生的是中断恰好在 RMW 和负载之间到达。
您还问了一个单独的问题:
can the output be 00 if without reordering ?
显然没有。没有程序顺序的交错可以将两个负载放在任一增量之前,因此 运行 时间或编译时间重新排序对于创建 00
效果是必要的。
a.fetch_add(1,memory_order_relaxed); // foo1
retb=b.load(memory_order_relaxed); // foo2
b.fetch_add(1,memory_order_relaxed); // bar1
reta=a.load(memory_order_relaxed); // bar2
随意混合,无需将 foo2 放在 foo1 之前或将 bar2 放在 bar1 之前。
即如果你单独单步执行每个线程,你永远看不到 00
。 当然 mo_relaxed
的全部意义在于它 可以 重新排序。指定“不重新排序”等同于“使用 seq_cst”。
存储缓冲区的效果是一种重新排序,specifically Store Load reordering。 mo_seq_cst
甚至可以防止这种情况,这是 seq_cst 的一部分,也是它如此昂贵的原因,尤其是对于纯商店运营。
这个问题是基于
我的电脑(core i3 x86_64)上从来没有得到 11,即 11 是一个有效的输出 iso c++ 中的 x86,所以我 缺少某些东西 吗? @Daniel Langr 证明 11 是 x86 上的有效输出。
现在x86_64有优势fetch_add作为全屏障
对于 arm64,由于 cpu 指令 reordering.
有时输出可能为 00对于 arm64 或其他一些 arch,如果 不重新排序,输出是否可以为 00?。我的问题 是基于此。函数 foo 的存储缓冲区值 a.fetch_add(1) 不可见 bar 的 a.load() b.fetch_add(1) 是 对于 foo 的 b.load() 不可见。因此我们得到 00 而无需重新排序。这会在不同架构的 ISO C++ 下发生吗?
// g++ -O2 -pthread axbx.cpp ; while [ true ]; do ./a.out | grep "00" ; done
#include<cstdio>
#include<thread>
#include<atomic>
using namespace std;
atomic<int> a,b;
int reta,retb;
void foo(){
a.fetch_add(1,memory_order_relaxed); //add to a is stored in store buffer of cpu0
//a.store(1,memory_order_relaxed);
retb=b.load(memory_order_relaxed);
}
void bar(){
b.fetch_add(1,memory_order_relaxed); //add to b is stored in store buffer of cpu1
//b.store(1,memory_order_relaxed);
reta=a.load(memory_order_relaxed);
}
int main(){
thread t[2]{ thread(foo),thread(bar) };
t[0].join(); t[1].join();
printf("%d%d\n",reta,retb);
return 0;
}
是的,ISO C++ 允许这样做;正如 Daniel 指出的那样,一种简单的方法是在 RMW 之后,加载之前放置一些慢速的东西,这样它们就不会执行,直到两个线程都有机会增加。这应该是显而易见的,因为它不需要任何 运行 时间重新排序发生,只是程序顺序的简单交错。 (所以 ISO C++ 允许 11
即使 seq_cst。)即你可以通过单独单步执行每个线程来练习这个。
您想知道如何在没有延迟循环的情况下在 x86 上创建实际演示吗?
尝试将原子变量放在单独的缓存行中,以便两个不同的内核可以并行写入它们。
alignas(64) std::atomic<int> a, b; // the alignas applies to each separately
由于它们在同一个缓存行中,就像默认情况下可能发生的那样,赢得了它们所在的缓存行所有权的核心将能够在增量的全屏障部分已完成。完成 RMW 意味着该内核的 L1d 缓存中的缓存行已经很热。 (核心只能在通过 MESI 获得独占所有权后修改缓存行,这也将使其对读取有效。)
所以一个线程的两个操作极有可能在另一个线程的任何一个操作之前发生。 (在 x86 asm 中,每个操作都具有与其 seq_cst 等价物相同的 asm,因此我们可以有效地讨论全局操作顺序而不会丢失任何内容。)
可能唯一能阻止这种情况发生的是中断恰好在 RMW 和负载之间到达。
您还问了一个单独的问题:
can the output be 00 if without reordering ?
显然没有。没有程序顺序的交错可以将两个负载放在任一增量之前,因此 运行 时间或编译时间重新排序对于创建 00
效果是必要的。
a.fetch_add(1,memory_order_relaxed); // foo1
retb=b.load(memory_order_relaxed); // foo2
b.fetch_add(1,memory_order_relaxed); // bar1
reta=a.load(memory_order_relaxed); // bar2
随意混合,无需将 foo2 放在 foo1 之前或将 bar2 放在 bar1 之前。
即如果你单独单步执行每个线程,你永远看不到 00
。 当然 mo_relaxed
的全部意义在于它 可以 重新排序。指定“不重新排序”等同于“使用 seq_cst”。
存储缓冲区的效果是一种重新排序,specifically Store Load reordering。 mo_seq_cst
甚至可以防止这种情况,这是 seq_cst 的一部分,也是它如此昂贵的原因,尤其是对于纯商店运营。