内存栅栏:软件高手的硬件观(三)
来源:互联网 发布:重庆seo俱乐部 编辑:程序博客网 时间:2024/05/29 10:27
因翻译水平有限,如有不妥,敬请指正和谅解!
原文下载地址:
http://download.csdn.net/download/programresearch/9829674
4. 存储序列导致不必要的停顿
不幸的是,每个存储缓冲区必须相对小,这意味着CPU执行一个适当的序列存储能填满它的储存缓冲区(例如,如果它们所有的都导致缓存未命中).在那时,CPU必须再一次等待无效去完成,为了排空它的存储缓冲,在它能继续执行前.此类情况可立即出现在内存栅栏之后,而所有后续存储指令必须等待使无效操作去完成,无论是否这些存储导致缓存未命中.
此状态可被改进通过使标记无效确认消息尽快到达.一种完成此的方式是在每个CPU使用无效消息队列,或”无效队列”.
4.1 无效队列
无效确认消息花费如此之长(时间)的原因之一,是因为必须确保对应的缓存行实际是无效的,并且此无效会被延迟,如果缓存忙,例如,如果CPU是集中的装载和存储所有这些被放置在缓存中的数据.另外,如果大量的无效消息在短时间内到达,给定的CPU可能在进度上落后处理它们,因此可能停顿所有其他的CPU.
然而,CPU不需要实际无效缓存行,在发送确认前.它将替代使用入队列无效消息,当知道,消息将被在CPU发送关于此缓存行任何进一步的消息前处理.
4.2 无效队列和无效确认
图7显示一个有无效队列的系统.一个有无效队列的CPU可在无效消息被放置入队列中时确认,替代必须等待直到对应的缓存行实际无效.当然,CPU必须引用它的无效队列,当准备发送无效消息时—-如果对应缓存行的一项在无效队列中,CPU不能立即发送无效消息;它必须替换等待直到无效队列项被处理完成.
放置一项到无效队列本质上是CPU允许在发送关于此缓存行的任何MESI协议消息前处理此项.只要对应的数据结构没有高并发,CPU将很少因此许可被打扰.
然而,事实上无效消息被缓存到无效队列中,提供了额外的内存重排的机会,如下一节讨论.
4.3 无效队列和内存栅栏
假设”a”和”b”的值初始化为0,即”a”是只读的副本(MESI”共享”状态),并且”b”由CPU0所有(MESI”独占”或”修改”状态).接着假设,CPU0执行foo(),而CPU1执行函数bar(),按下面代码片段:
1 void foo(void)2 {3 a = 1;4 smp_mb();5 b = 1;6 }78 void bar(void)9 {10 while(b == 0) continue;11 assert(a == 1);12 }
接着操作序列可能如下:
1.CPU0执行a=1.对应的缓存行在CPU0缓存中是只读的,因此CPU0放置”a”的新值在它的存储缓冲中,并且发送一个无效消息为了刷新来自于CPU1的缓存的对应的缓存行.
2.CPU1执行while(b==0)continue,但包含”b”的缓存行不在它的缓存中.它因此发送一个”读”消息.
3.CPU0执行b=1,它已经拥有此缓存行(换句话说,缓存行已经在”修改”或”独占”状态下),因此它存储”b”的新值到它的缓存行中.
4.CPU0接收”读”消息,并发送包含最新更新的”b”的缓存行到CPU1,也在它自己的缓存中标记行为”共享”.
5.CPU1接收”a”的无效消息,放置它到它的无效队列,并发送一个”无效确认”消息到CPU0.注意:”a”的旧值仍然存在于CPU1的缓存中.
6.CPU1接收包含”b”的缓存行,并装载它到它的缓存.
7.CPU1现在可以结束执行while(b==0)continue,并由于它找到”b”的值为1,它继续下一条语句.
8.CPU1执行assert(a==1),并且由于”a”的旧值仍在CPU1的缓存中,此断言失败.
9.CPU1处理队列的”无效”消息,并从它自己的缓存中无效包含”a”的缓存行.但这太迟了.
10.CPU0接收到来至CPU0(译者注:应为CPU1)对于”a”的”无效确认”消息,并立即应用缓冲的存储到断言失败的受害者CPU1.
对此情况,CPU设计者再一次地无计可施,由于硬件不可能知道CPU放置的不同bits之间的关系.然而,内存栅栏指令能交互影响无效队列,因此当一个给定CPU执行内存栅栏,它标记所有当前在它的无效队列中的项,并强制后续任何装载去等待直到所有标记项被应用到CPU缓存.
因此,可以添加一个内存栅栏如下:
1 void foo(void)2 {3 a = 1;4 smp_mb();5 b = 1;6 }78 void bar(void)9 {10 while(b == 0)continue;11 smp_mb();12 assert(a == 1);13 }
有了这些改变,操作序列可能如下:
1.CPU0执行a=1.在CPU0的缓存中对应的缓存行是只读的,因此CPU0放置”a”的新值到它的存储缓冲中,并发送一个”无效消息”,为了刷新来至CPU1缓存的对应的缓存行.
2.CPU1执行while(b==0)continue,但包含”b”的缓存行不在它的缓存中.它因此发送一个读消息.
3.CPU0执行b=1,它已经拥有此缓存行(也就是说,缓存行已经在”修改”或”独占”状态下),因此它存储”b”的新值到它的缓存行中.
4.CPU0接收”读”消息,并发送包含最新”b”值的缓存行到CPU1,也在它自己的缓存中将行标记为”共享”.
5.CPU1接收对于”a”的”无效消息”,放置它到它的无效队列,并发送一个”无效确认”消息到CPU0.注意:”a”的旧值仍然保留在CPU1的缓存中.
6.CPU1接收包含”b”的缓存行,并装载它到它的缓存.
7.CPU1能现在完成执行while(b==0)continue;并由于它找到”b”的值为1,它继续下一条语句.
8.CPU1执行smp_mb(),标记项在它的无效队列.
9.CPU1执行assert(a==1),但由于对于包含”a”的缓存行在无效队列中有标记,CPU1必须停顿此装载直到无效队列中的项被应用.
10.CPU1处理”无效”消息,移除包含”a”的缓存行从它的缓存中.
11.CPU1现在可以自由的装载”a”的值,但由于此导致一个缓存未命中,它必须发送”读”消息去获取对应的缓存行.
12.CPU0接收来自CPU0(译者注:CPU1)对于”a”的”无效确认”消息,并因此应用缓冲存储,改变对应缓存行的MESI状态为”修改”.
13.CPU0接收来自CPU1对”a”的”读”消息,并因此改变对应缓存行的状态为”共享”,并发送缓存行到CPU1.
14.CPU1接收包含”a”的缓存行,并因此可完成装载.由于此装载返回”a”的更新值,断言通过.
有大量的MESI消息的传递,CPU得到正确的结果.
5. 读和写内存栅栏
在前面的章节,内存栅栏被同时用于标记存储缓存和无效队列的项,但在代码段中,foo()没有理由去做任何关于无效队列的事,而bar()同样没有理由去做任何关于存储队列的事.
很多CPU结构,因而提供更弱一些的内存栅栏指令,只做两者之一.大致来说,一个”读内存栅栏”仅标记无效队列,并且一个”写内存栅栏”仅标记存储缓冲.而一个完整的内存栅栏两者都做.
效果是,读内存栅栏仅排序CPU的装载并执行,因此所有在读内存栅栏之前的装载,将视为在读内存栅栏后的任何装载之前完成.类似,写内存栅栏仅排序CPU的存储,并在CPU上执行,并再一次因此所有在写内存栅栏之前的存储,将视为在写内存栅栏后的任何存储之前完成.一个完整的内存栅栏同时排序装载和存储,但仅在执行内存栅栏的CPU上.
如果更新foo和bar去使用读和写内存栅栏,显示如下:
1 void foo(void)2 {3 a = 1;4 smp_wmb();5 b = 1;6 }78 void bar(void)9 {10 while(b == 0)continue;11 smp_rmb();12 assert(a == 1);13 }
一些计算机有更多种类的内存栅栏,但通常理解这三种将提供一种好的内存栅栏引导.
- 内存栅栏:软件高手的硬件观(三)
- 内存栅栏:软件高手的硬件观(一)
- 内存栅栏:软件高手的硬件观(二)
- 内存栅栏:软件高手的硬件观(四)
- 我是硬件领域的软件高手,是软件领域的硬件高手!
- 高手的经验 硬件
- 【转帖】硬件高手的经验
- 软件、硬件的关系
- JAVA中的内存栅栏
- 一个硬件高手的设计经验分享
- 硬件高手的设计经验分享
- 一个硬件高手的设计经验分享
- 一个硬件高手的设计经验分享
- 一个硬件高手的设计经验分享
- 新手电脑硬件软件故障解答(三)
- 软件高手的六个阶段
- 软件决定硬件的发挥,硬件决定软件的结果。
- SoC嵌入式软件架构设计之二:内存管理单元的软、硬件协同设计
- 排序算法之冒泡排序
- deepin开发环境配置笔记
- Hive 自定义函数(UDF)开发
- windows远程桌面设置
- iOS
- 内存栅栏:软件高手的硬件观(三)
- spring配置文件命名空间插件springsource-tool-suite
- Android VR Player(全景视频播放器) [4]:侧滑菜单的实现
- 深刻分析有效值与均方根
- Android Camera Preview ANativeWindow的处理
- 算法谜题91 水平的和垂直的多米诺骨牌
- jedis基本操作
- Ubuntu aria2c 下载
- 算法导论——二叉查找树