MIPS架构深入理解10-向MIPS移植软件之内存序

简介: MIPS架构深入理解10-向MIPS移植软件之内存序
  • 1 内存访问的排序和重新排序
  • 2 访存顺序和写缓存
  • 3 写缓存的flush

站在巨人的肩膀上,才能看得更远。

If I have seen further, it is by standing on the shoulders of giants.


牛顿


这是向MIPS架构移植软件的问题系列之第三篇。在前两篇文章

*《MIPS架构深入理解8-向MIPS架构移植软件之大小端问题》

*《MIPS架构深入理解9-向MIPS移植软件之Cache管理》

中,我们分别讨论了大小端模式和Cache对于移植代码的影响。那么本文,我们再从内存序理解一下对于移植代码的影响,尤指底层代码或操作系统代码。


1 内存访问的排序和重新排序


程序员往往认为他们的代码是顺序执行的:CPU执行指令,更新系统的状态,然后继续下一条指令。但是,如果允许CPU乱序执行,而不是这种串行方式执行,效率可能更高。这对于执行load和store这种存储指令尤其重要。

从CPU的角度来看,执行store操作就是发送一个write请求:给出内存地址和数据,其余的交给内存控制器完成。实际的内存和I/O设备相对较慢,等write操作完成,CPU可能已经完成了几十条甚至几百条指令。

read操作又有不同:它需要发送一个read请求,然后等待对请求的响应。当CPU需要知道内存或者设备寄存器中的内容时,没有得到请求响应前,可能啥也做不了。

如果想要追求更高的性能,就意味着我们需要让read尽可能地快,甚至不惜让write操作变得更慢。进一步考虑,我们可以让write操作排队等待,把随后的任何read操作请求提前到write请求队列之前执行。从CPU的角度来看,这是一个大优势:尽可能快地启动read操作,就越早得到read操作的响应。然后,在某个时刻把执行write操作,而且write请求队列的大小是固定的。但是,这个write操作可能需要写Cache一段时间。如果这个队列满了,可能需要停下来等待一段时间,等待所有的write完成操作。但是,这肯定要比顺序执行,效率更高。这就是现代CPU一般都具有一个write buffer的原因。

看到这儿,你可能会有一个疑问:某些程序可能会写入一个地址,然后再将其读回来,这时候会怎么样呢?如果read提前到write之前执行,我们可能从内存中读取的是旧值,从而导致程序发生故障。通常,CPU会提供额外的硬件,比较read操作的地址和write队列中的地址,如果有相同的项,就不允许这样的read操作提前到write操作之前执行。

上面的讨论没有考虑真正的并发系统,比如多核系统。并发执行的任务间共享变量,对其执行read和write操作会非常危险。比如使用共享变量进行同步和通信的时候,内存访问次序就会非常重要。这种情况下,软件一般会采用精心的设计,比如锁和信号量,进行同步操作。

但是,使用共享内存,还有一些技巧,往往效果更好,开销也更小。因为不需要使用信号量或者锁。但是,可能会被乱序执行打断。假设,我们有2个任务,如下图所示:一个读取数据结构,一个写数据结构。它们可以交替使用这个数据结构。


640.png

为了能够正确执行,我们需要知道,对于reader任务来说,当什么时候reader任务看见关键域中的值发生了更新时,能够保证其它所有的更新对reader任务可见。

当然,硬件可以实现所有的内存访问顺序问题,从而将它们对程序员不可见,但是也就放弃了解耦read和write操作带来的性能优势。MIPS架构提供了sync指令实现这个目的,它可以确保sync指令之前的访问先于之后的执行。但是,这种保障指令有其局限性:只与内存的访问顺序有关,只能被非Cache或具有Cache一致性的内存访问的参与者看见。

对于上面的示例,为了让其在合适的系统上可靠地运行,writer任务应该在写关键域的值之前,调用一条sync指令;reader任务应该在读关键域的值之后插入一条sync指令。对于sync指令的详细使用方法,可以参考《MIPS指令集参考大全》一文。

不同的体系架构对执行顺序作出了不同的规定。一类极端情况就是,要求所有的CPU和系统设计人员努力保证一个CPU的全部读和写操作,从另一个CPU的角度看上去顺序完全相同,这叫做强序。也有一类情况就是弱序,比如只要求所有的写操作保持顺序不变。而MIPS架构更为激进,完全就是无序访问内存。这就要求我们系统开发人员必须手动保证内存的访问顺序是正确的。


2 访存顺序和写缓存


前面讨论了这么多理论,接下来让我们讨论点实际的内容吧。把write操作缓存到一个队列中(也就是硬件中常常讨论的write buffer)的思想在实践中证明非常有效。因为,store指令往往是多条指令扎堆出现。比如,一个运行MIPS代码的CPU,实际上运行的store指令大约占所有指令的10%左右;但是,往往是突发式访问,比如函数的调用过程中,首先需要压栈操作一组寄存器的值。

但是,一般情况下,写缓存(英文称为write buffer)都是硬件保证的,对于软件来说不用管理。但是,也有一些特殊的情况,程序员需要知道怎样处理:

  1. I/O寄存器访问的时序
    这个问题,对于所有架构CPU都存在。比如,CPU发出一个store指令,更新I/O设备寄存器的值,write请求可能会在写缓存中延迟一段时间。这时候,可能会发生其它事件,比如中断。但是此时写入的值还未更新到对应的I/O设备寄存器中。这可能导致一些奇怪的行为:比如,你想禁止产生中断,但是CPU发出write操作之后,CPU还有可能会收到中断。
  2. read操作抢先于write操作执行
    上面已经讨论过,MIPS32/64架构允许这种操作。如果想要软件更加健壮和具有可移植性,就不应该假定read和write操作顺序会被保持。如果想要保证前后两个指令周期是按照特定顺序执行,就需要插入sync指令。
  3. 字节汇集
    有些写缓存会汇集不足WORD大小的write操作,凑成一个WORD大小的write操作,然后再执行(有些写缓存甚至会攒一个Cache行,然后再写入)。所以,为了避免对于非Cache的内存区也做相同的操作,最好的办法就是把I/O寄存器(比如,一个8位的寄存器)映射到一个单独的WORD大小的地址上。


3 写缓存的flush


通过对非Cache内存区的任意位置执行write操作,然后再read,可以清空写缓存(大部分都是这样实现的)。当然,写缓存不允许read操作发生在write之前,这样导致返回旧值。所以,必须在write和read操作之间,插入sync指令。对于兼容MIPS32/64规范的任何系统,这应该都是有效的。

但是,有效不等于高效。通过提高内存的读写速度也可以降低整体的负荷。有些特定的系统可能会提供更快的内存或者写缓存。

任何具有回写功能的处理器或者内存接口,都引入了写缓存。只是,有的在CPU内部实现,有的在CPU外部实现。不管是在CPU内部,还是在CPU外部,麻烦是相同的。在编程的时候,一定要仔细确认你的系统中,写缓存的位置,善加利用。

相关文章
|
1月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
26 2
|
2月前
|
存储 前端开发 Java
Android MVVM架构模式下如何避免内存泄漏
Android采用MVVM架构开发项目,如何避免内存泄漏风险?怎样避免内存泄漏?
110 1
|
1月前
|
运维 负载均衡 Shell
控制员工上网软件:高可用架构的构建方法
本文介绍了构建控制员工上网软件的高可用架构的方法,包括负载均衡、数据备份与恢复、故障检测与自动切换等关键机制,以确保企业网络管理系统的稳定运行。通过具体代码示例,展示了如何实现这些机制。
122 63
|
27天前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
101 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
4月前
|
人工智能 运维 虚拟化
完善多云平台软件体系,VMware再探索下一代企业IT架构
完善多云平台软件体系,VMware再探索下一代企业IT架构
|
1月前
|
Kubernetes 前端开发 分布式数据库
工作中常见的软件系统部署架构
在实际应用中,会根据项目的具体需求、规模、性能要求等因素选择合适的部署架构,或者综合使用多种架构模式来构建稳定、高效、可扩展的系统。
186 2
|
3月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
2月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
|
2月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
60 0
|
4月前
|
边缘计算 物联网 5G
软件定义网络(SDN)的未来趋势:重塑网络架构,引领技术创新
【8月更文挑战第20天】软件定义网络(SDN)作为新兴的网络技术,正在逐步重塑网络架构,引领技术创新。随着5G、人工智能、边缘计算等技术的不断发展,SDN将展现出更加广阔的应用前景和市场潜力。未来,SDN有望成为主流网络技术,并在各行各业推动数字化转型。让我们共同期待SDN技术带来的更加智能、安全和高效的网络体验。