asm volatile("" ::: "memory") 的生命周期是多少?
What is the life-time of asm volatile("" ::: "memory")?
我已经阅读了很多关于编译障碍和内存障碍的解释,但我还不确定编译器如何知道编译内存排序的预防从哪里开始和结束。 (另一方面,我了解 cpu 内存屏障的工作原理...)
下面是一个任意示例,没有编译障碍。
int func(int *x, int *y) {
int var = 0;
x[0] += 1;
var += y[0];
y[0] += 1;
x[1] += 1;
var += y[1];
y[0] += 1;
return var;
}
例如,如果我只想在这个函数中阻止编译内存排序,而不是在其他函数中,我应该在函数末尾插入 asm volatile("" :::: "memory") , 在返回 var?
之前
喜欢:
int func(int *x, int *y) {
int var = 0;
x[0] += 1;
var += y[0];
y[0] += 1;
x[1] += 1;
var += y[1];
y[0] += 1;
asm volatile("" ::: "memory");
return var;
}
无论你把它放在哪里,屏障都会阻止重新排序(或优化)。没有什么神奇的"scope"。看看 inline assembly instruction:
asm volatile (""::: "memory");
volatile
关键字的意思是把asm
语句准确地放在我放的地方,不要把它优化掉(即去掉) .在第三个 :
之后是 clobbers 的列表,所以这意味着 "I have clobbered the memory." 你基本上是在告诉编译器 "I have done something to affect the memory."
在你的例子中,你有类似的东西
y[0] += 1;
y[0] += 1;
编译器非常聪明,知道这并没有达到应有的效率。它可能会把它编译成类似
的东西
load y[0] from memory to register
add 2 to this register
store result to y[0]
由于pipelining的原因,将此与其他load/modify/store操作结合起来可能也会更有效。因此,编译器可能会通过将其与附近的操作合并来进一步重新排序。
为防止这种情况,您可以在它们之间放置一个内存屏障:
y[0] += 1;
asm volatile (""::: "memory");
y[0] += 1;
这告诉编译器,在第一条指令之后,"I have done something to the memory, you may not know about it, but it happened." 所以它不能使用它的标准逻辑,并假设向同一内存位置添加一个两次与向其添加两个相同,因为某些东西发生在两者之间。所以这会被编译成更像
的东西
load y[0] from memory to register
add 1 to this register
store result to y[0]
load y[0] from memory to register
add 1 to this register
store result to y[0]
同样,它可能会重新排序障碍每一侧的东西,但不能跨越它。
另一个例子:有一次,我在微控制器上使用内存映射 I/O。编译器看到我正在向同一个地址写入不同的值,中间没有读取,所以它友好地将其优化为最后一个值的单次写入。当然,这使我的 I/O activity 无法按预期工作。在写入之间放置一个内存屏障告诉编译器不要这样做。
我已经阅读了很多关于编译障碍和内存障碍的解释,但我还不确定编译器如何知道编译内存排序的预防从哪里开始和结束。 (另一方面,我了解 cpu 内存屏障的工作原理...)
下面是一个任意示例,没有编译障碍。
int func(int *x, int *y) {
int var = 0;
x[0] += 1;
var += y[0];
y[0] += 1;
x[1] += 1;
var += y[1];
y[0] += 1;
return var;
}
例如,如果我只想在这个函数中阻止编译内存排序,而不是在其他函数中,我应该在函数末尾插入 asm volatile("" :::: "memory") , 在返回 var?
之前喜欢:
int func(int *x, int *y) {
int var = 0;
x[0] += 1;
var += y[0];
y[0] += 1;
x[1] += 1;
var += y[1];
y[0] += 1;
asm volatile("" ::: "memory");
return var;
}
无论你把它放在哪里,屏障都会阻止重新排序(或优化)。没有什么神奇的"scope"。看看 inline assembly instruction:
asm volatile (""::: "memory");
volatile
关键字的意思是把asm
语句准确地放在我放的地方,不要把它优化掉(即去掉) .在第三个 :
之后是 clobbers 的列表,所以这意味着 "I have clobbered the memory." 你基本上是在告诉编译器 "I have done something to affect the memory."
在你的例子中,你有类似的东西
y[0] += 1;
y[0] += 1;
编译器非常聪明,知道这并没有达到应有的效率。它可能会把它编译成类似
的东西load y[0] from memory to register add 2 to this register store result to y[0]
由于pipelining的原因,将此与其他load/modify/store操作结合起来可能也会更有效。因此,编译器可能会通过将其与附近的操作合并来进一步重新排序。
为防止这种情况,您可以在它们之间放置一个内存屏障:
y[0] += 1;
asm volatile (""::: "memory");
y[0] += 1;
这告诉编译器,在第一条指令之后,"I have done something to the memory, you may not know about it, but it happened." 所以它不能使用它的标准逻辑,并假设向同一内存位置添加一个两次与向其添加两个相同,因为某些东西发生在两者之间。所以这会被编译成更像
的东西load y[0] from memory to register add 1 to this register store result to y[0] load y[0] from memory to register add 1 to this register store result to y[0]
同样,它可能会重新排序障碍每一侧的东西,但不能跨越它。
另一个例子:有一次,我在微控制器上使用内存映射 I/O。编译器看到我正在向同一个地址写入不同的值,中间没有读取,所以它友好地将其优化为最后一个值的单次写入。当然,这使我的 I/O activity 无法按预期工作。在写入之间放置一个内存屏障告诉编译器不要这样做。