问题 记忆围栏 - 需要帮助才能理解


我正在阅读Paul E. McKenney的“记忆障碍” http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf 一切都被详细解释,当我看到一切都清楚时,我会遇到一句话,这会使一切都变得愚蠢,让我觉得我一无所知。让我举个例子

void foo(void)
{
   a = 1; #1
   b = 1; #2
}

void bar(void)
{
   while (b == 0) continue; #3
   assert(a == 1); #4
}

假设这两个函数在不同的处理器上运行。 现在可能发生的事情是第二个处理器在存储到b#2之后可以看到存储到#1,因为第一个处理器队列存储到“a”并继续存储b指令。好的,没关系,我们在#1和#2之间的行中添加了一个写入栅栏,但是这段代码仍然可能失败,因为第二个处理器可能会对invalidate消息进行排队,所以我们再添加一个内存栅栏(这次读取栅栏) #4和#4之间的界限。

void foo(void)
{
   a = 1; #1
   write_memory_barrier();
   b = 1; #2
}

void bar(void)
{
   while (b == 0) continue; #3
   read_memory_barrier();
   assert(a == 1); #4
}

这强制第二个处理器处理所有排队的消息(使a无效)并通过向#4上的第一个处理器发送读MESI消息再次读取它。好。接下来文章说

因此,许多CPU架构   提供较弱的记忆障碍   仅执行一个或多个的指令   这两个中的另一个。大致说来,   “读取内存屏障”仅标记了   使队列无效并“写入内存”   屏障“仅标记存储缓冲区。   一个完整的记忆障碍   做到了。

很好,很清楚,但在那之后,我看到了这一点

这样做的效果就是阅读   内存屏障订单仅加载   执行它的CPU,以便所有   读取存储器之前的负载   障碍似乎已经完成   在读取之后的任何加载之前   记忆障碍。同样,写一个   内存屏障订单只有商店,   再次在执行它的CPU上,和   再次使所有商店前面的   写内存屏障会出现   在任何商店之前完成   遵循写入内存屏障。

所以

读取存储器屏障之前的所有负载都将   似乎在任何负载跟随之前已经完成   读取内存屏障

混合了以前解释过的所有内容。这是什么意思?功能“bar”中的哪个负载必须在加载“a”#4之前完成?我理解断言在这个函数中没有内存障碍就会失败,因为处理器可能会读取一个旧值,因为它仍然无法使对象“a”所在的高速缓存行无效。

细节上的解释会非常有帮助,我试着整天都在理解它。

首先十分感谢。


5000
2017-10-22 17:41


起源



答案:


这是什么意思?

这意味着如果你有:

read
read
read
READ BARRIER
read
read
read

然后,读屏障充当将这些读数分成两批的“连接点”。读取屏障之前的所有读取将在读取屏障开始之后的任何读取之前完成。

哪个载入 bar() 必须在装载之前完成 a (#4)开始了吗?

所有阅读 b (#3)被迫在任何阅读之前 a(#4)。这意味着 a 直到后才读 b 不再是0.因为 foo() 使用写屏障来确保 a 已经改为 1 (#1)当时 b 改变了(#2)。因此,这两个障碍共同起作用,以确保断言声明始终成功。


10
2017-10-22 18:20



感谢您的回答,但是如果我们看一下文章中的示例点4.3,我们会看到一个示例,当实际上所有读取的b(#3)都在读取没有内存屏障并且断言仍然失败时,因为CPU 1执行了断言(a == 1),并且,由于“a”的旧值仍在CPU 1的高速缓存中,因此该断言失败。 - confucius
在固定代码(带有读屏障)中,CPU 1执行断言(a == 1),并且由于包含“a”的高速缓存行不再在CPU 1的高速缓存中(因为读取屏障被迫使高速缓存行无效),发送“读取”消息。 - confucius
我认为问题在于总结处于更高的抽象层次。暂时忘记缓存,并想象所有读取实际上是在读取屏障之前/之后第一次执行。这是使高速缓存无效的最终结果:处理器被强制执行读取 a 在它击中读屏障之后。没有阅读障碍,它可以自由阅读 a 然后循环直到 b 不为零。屏障确保了阅读 a 在屏障之前,即之前没有重新排序 b 非零。 - Jeremy W. Sherman
顺便说一下,你可能会找到这篇文章 x86-TSO:x86多处理器的严格且可用的程序员内存模型 作者:Owens,Sarkar和Sewell,发表于2010年5月的CACM,对于思考这些事情非常有帮助。 - Jeremy W. Sherman
还有一个 扩展版的 x86-TSO 纸 包括校样大纲和经过验证的检查员。在CACM上发表的那篇文章实际上只是一个“研究亮点”。那就是说,这是我读过的版本,绝对值得花时间。如果有机会,请阅读CACM实际版本中的版本;图表和表格比我链接到的最终草稿预印本更漂亮,更易读。 - Jeremy W. Sherman


答案:


这是什么意思?

这意味着如果你有:

read
read
read
READ BARRIER
read
read
read

然后,读屏障充当将这些读数分成两批的“连接点”。读取屏障之前的所有读取将在读取屏障开始之后的任何读取之前完成。

哪个载入 bar() 必须在装载之前完成 a (#4)开始了吗?

所有阅读 b (#3)被迫在任何阅读之前 a(#4)。这意味着 a 直到后才读 b 不再是0.因为 foo() 使用写屏障来确保 a 已经改为 1 (#1)当时 b 改变了(#2)。因此,这两个障碍共同起作用,以确保断言声明始终成功。


10
2017-10-22 18:20



感谢您的回答,但是如果我们看一下文章中的示例点4.3,我们会看到一个示例,当实际上所有读取的b(#3)都在读取没有内存屏障并且断言仍然失败时,因为CPU 1执行了断言(a == 1),并且,由于“a”的旧值仍在CPU 1的高速缓存中,因此该断言失败。 - confucius
在固定代码(带有读屏障)中,CPU 1执行断言(a == 1),并且由于包含“a”的高速缓存行不再在CPU 1的高速缓存中(因为读取屏障被迫使高速缓存行无效),发送“读取”消息。 - confucius
我认为问题在于总结处于更高的抽象层次。暂时忘记缓存,并想象所有读取实际上是在读取屏障之前/之后第一次执行。这是使高速缓存无效的最终结果:处理器被强制执行读取 a 在它击中读屏障之后。没有阅读障碍,它可以自由阅读 a 然后循环直到 b 不为零。屏障确保了阅读 a 在屏障之前,即之前没有重新排序 b 非零。 - Jeremy W. Sherman
顺便说一下,你可能会找到这篇文章 x86-TSO:x86多处理器的严格且可用的程序员内存模型 作者:Owens,Sarkar和Sewell,发表于2010年5月的CACM,对于思考这些事情非常有帮助。 - Jeremy W. Sherman
还有一个 扩展版的 x86-TSO 纸 包括校样大纲和经过验证的检查员。在CACM上发表的那篇文章实际上只是一个“研究亮点”。那就是说,这是我读过的版本,绝对值得花时间。如果有机会,请阅读CACM实际版本中的版本;图表和表格比我链接到的最终草稿预印本更漂亮,更易读。 - Jeremy W. Sherman