在msdn http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208(v=vs.85).aspx上,MemoryBarrier是作为对xchg的调用来实现的。
// x86
FORCEINLINE
VOID
MemoryBarrier (
VOID
)
{
LONG Barrier;
__asm {
xchg Barrier, eax
}
}
Run Code Online (Sandbox Code Playgroud)
我在“软件开发人员手册”中找不到一些材料。请告诉我原因。
Vulkan 规范 (1.0.27) 表示(在第6.5 节“管道障碍”中):
pMemoryBarriers、pBufferMemoryBarriers 和 pImageMemoryBarriers 数组的每个元素指定内存依赖性的两半,如上面所定义。[...]
如果在渲染通道实例之外调用 vkCmdPipelineBarrier,则第一组命令是提交到队列并记录在命令缓冲区中的所有先前命令,第二组命令是记录在命令缓冲区中并提交到队列的所有后续命令。
(这个措辞很有趣;如果从字面上解释,它似乎是说屏障只在一个命令缓冲区内对命令进行排序,而“提交到队列”部分可能是多余的;但如果解释得更模糊一点,它似乎旨在说屏障在其命令缓冲区和队列中对命令进行排序。其他 Stack Overflow 页面向我指出了以下内容,这似乎证实了后一种解释: https: //github.com/KhronosGroup/Vulkan-Docs/issues/300)
那么我的问题是。假设您有四个命令缓冲区,分两批提交,每批提交两个,全部在一个vkQueueSubmit命令中:
VkSubmitInfo nextSubmitInfo;
nextSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
nextSubmitInfo.pNext = nullptr;
nextSubmitInfo.waitSemaphoreCount = 0;
nextSubmitInfo.pWaitDstStageMask = nullptr;
nextSubmitInfo.pWaitSemaphores = nullptr;
nextSubmitInfo.signalSemaphoreCount = 0;
nextSubmitInfo.pSignalSemaphores = nullptr;
std::vector<VkCommandBuffer> commandBuffersAB{commandBufferA, commandBufferB};
std::vector<VkCommandBuffer> commandBuffersCD{commandBufferC, commandBufferD};
std::vector<VkSubmitInfo> submitInfo;
nextSubmitInfo.commandBufferCount = commandBuffersAB.size();
nextSubmitInfo.pCommandBuffers = commandBuffersAB.data();
submitInfo.emplace_back(nextSubmitInfo);
nextSubmitInfo.commandBufferCount = commandBuffersCD.size();
nextSubmitInfo.pCommandBuffers = commandBuffersCD.data();
submitInfo.emplace_back(nextSubmitInfo);
df.vkQueueSubmit(queue, submitInfo.size(), submitInfo.data(), VK_NULL_HANDLE);
Run Code Online (Sandbox Code Playgroud)
假设四个命令缓冲区中的每一个都包括屏障和一些动作命令(根据规范,这些命令是“执行动作(例如绘制/调度)的命令”)。那么,我倾向于天真地期望,障碍会将命令缓冲区视为按字母顺序提交,以便它们的第一和第二“一半”将包括(可能除其他外)以下内容:
| 屏障| 上半场| 下半场| |--------------------------------|---------------------------------------- -|----------------------------| | …
我的测试代码如下,我发现只有memory_order_seq_cstforbade编译器重新排序.
#include <atomic>
using namespace std;
int A, B = 1;
void func(void) {
A = B + 1;
atomic_thread_fence(memory_order_seq_cst);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
而其他选择memory_order_release,memory_order_acq_rel根本没有产生任何编译屏障.
我认为他们必须使用原子变量,如下所示.
#include <atomic>
using namespace std;
atomic<int> A(0);
int B = 1;
void func(void) {
A.store(B+1, memory_order_release);
B = 0;
}
Run Code Online (Sandbox Code Playgroud)
但我不想使用原子变量.与此同时,我认为"asm("":::"记忆")"太低了.
还有更好的选择吗?
据我所知,内存屏障用于避免乱序执行。但是,在谈论缓存一致性时,也经常提到存储障碍。我不确定这两个概念是如何连接的,因为-根据我的发现-缓存一致性应该已经通过各种协议(例如MESI等)在硬件级别上得到了保证。使用内存屏障来防止乱序执行是(手动)授予缓存一致性的另一种方法吗?
assembly multithreading cpu-architecture memory-barriers cpu-cache
正如问题所述,我对内存屏障和仅编译器围栏之间的区别感到困惑。
它们是一样的吗?如果不是的话它们之间有什么区别?
该程序有时会打印 00,但如果我注释掉 a.store 和 b.store 并取消注释 a.fetch_add 和 b.fetch_add ,它们执行完全相同的操作,即都设置 a=1,b=1 的值,我从不得到00。(在 x86-64 Intel i3 上测试,使用 g++ -O2)
我是不是遗漏了什么,或者按照标准“00”永远不会出现?
这是带有普通商店的版本,可以打印00。
// 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);
a.store(1,memory_order_relaxed);
retb=b.load(memory_order_relaxed);
}
void bar(){
//b.fetch_add(1,memory_order_relaxed);
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;
}
Run Code Online (Sandbox Code Playgroud)
下面从不打印 00
// g++ -O2 -pthread axbx.cpp …Run Code Online (Sandbox Code Playgroud) c++ multithreading cpu-architecture memory-barriers stdatomic
我试图了解整个 L1/L2 冲洗是如何工作的。假设我有一个像这样的计算着色器
layout(std430, set = 0, binding = 2) buffer Particles{
Particle particles[];
};
layout(std430, set = 0, binding = 4) buffer Constraints{
Constraint constraints[];
};
void main(){
const uint gID = gl_GlobalInvocationID.x;
for (int pass=0;pass<GAUSS_SEIDEL_PASSES;pass++){
// first query the constraint, which contains particle_id_1 and particle_id_1
const Constraint c = constraints[gID*GAUSS_SEIDEL_PASSES+pass];
// read newest positions
vec3 position1 = particles[c.particle_id_1].position;
vec3 position2 = particles[c.particle_id_2].position;
// modify position1 and position2
position1 += something;
position2 -= something;
// update positions
particles[c.particle_id_1].position = …Run Code Online (Sandbox Code Playgroud) 据我了解,编写并随后执行 JIT 或自修改代码的一般、更抽象的过程如下所示。
从这篇关于 x86 上自修改代码的文章中我可以看出,手动缓存管理显然是没有必要的。我认为 aclflushopt是必要的,但 x86 1显然会在从具有新指令的位置加载时自动处理缓存失效,这样指令提取就永远不会过时。我的问题不是关于 x86,但我想将其包括在内以进行比较。
AArch64 中的情况稍微复杂一些,因为它区分了可共享域以及缓存操作的“可见性”程度。仅从 ARMv8/ARMv9 的官方文档中,我首先得出了这个猜测。
dsb ishst确保在继续之前已全部写入,isb sy确保从内存中提取后续指令。但DMB/DSB/ISB 的文档说“ISB 后面的指令是从缓存或内存中获取的”。这给我的印象是缓存控制操作确实是必要的。我的新猜测是这样的。
dsb ishst确保在继续之前已全部写入,ic ivau新代码占用的所有缓存行。但我又忍不住觉得这也不太对劲。过了一会儿,我在文档中发现了一些我错过的东西,并且在一篇论文中发现了几乎相同的东西。他们都给出了一个看起来像这样的例子。
dc cvau, Xn ; Clean cache to PoU, so the newly written code will be visible
dsb ish ; Wait for cleaning to finish
ic ivau, Xn ; Invalidate …Run Code Online (Sandbox Code Playgroud) 在我的理解中,CPU为了优化而改变了写在机器代码上的操作顺序,这被称为乱序执行。
在术语“内存顺序”中,它定义了访问内存的顺序。例如,在宽松的顺序中,它定义了非常弱的排序规则,并且很容易发生执行重排序。
x86 中有一些内存排序模型,例如 TSO。在这样的存储器排序模型中,定义了处理器的存储器访问顺序的语义。
我不明白的是他们之间的关系。内存顺序是一种乱序执行吗?OoOe还有其他方法吗?
或者说,内存顺序是乱序执行的实现,处理器的所有重新排序都是基于语义的吗?
我一直在阅读Jeff Preshing 的这篇关于 Synchronizes-With Relation 的文章,以及cpp 参考中的std::memory_order页面中的“Release-Acquire Ordering”部分,但我不太明白:
似乎标准有某种承诺,但我不明白为什么有必要。让我们以 CPP 参考中的示例为例:
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
Run Code Online (Sandbox Code Playgroud)
参考文献解释说:
如果线程 A 中的原子存储标记为 memory_order_release,并且线程 B …