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
Android MVVM架构模式下如何避免内存泄漏
Android采用MVVM架构开发项目,如何避免内存泄漏风险?怎样避免内存泄漏?
88 1
|
3月前
|
人工智能 运维 虚拟化
完善多云平台软件体系,VMware再探索下一代企业IT架构
完善多云平台软件体系,VMware再探索下一代企业IT架构
|
2月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
1月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
|
1月前
|
存储 前端开发 Java
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
Kotlin教程笔记 - MVVM架构怎样避免内存泄漏
51 0
|
3月前
|
边缘计算 物联网 5G
软件定义网络(SDN)的未来趋势:重塑网络架构,引领技术创新
【8月更文挑战第20天】软件定义网络(SDN)作为新兴的网络技术,正在逐步重塑网络架构,引领技术创新。随着5G、人工智能、边缘计算等技术的不断发展,SDN将展现出更加广阔的应用前景和市场潜力。未来,SDN有望成为主流网络技术,并在各行各业推动数字化转型。让我们共同期待SDN技术带来的更加智能、安全和高效的网络体验。
|
3月前
|
设计模式 uml
在电脑主机(MainFrame)中只需要按下主机的开机按钮(on()),即可调用其它硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘(Hard
该博客文章通过一个电脑主机启动的示例代码,展示了外观模式(Facade Pattern)的设计模式,其中主机(MainFrame)类通过调用内部硬件组件(如内存、CPU、硬盘)和操作系统的启动方法来实现开机流程,同时讨论了外观模式的优缺点。
|
3月前
|
Java 数据安全/隐私保护
一种优秀的虚拟机内存架构 - AQ
【8月更文挑战第8天】AQ虚拟机内存架构是一种创新设计,旨在提供高效、可靠及灵活的内存管理。它通过精细划分内存并采用智能分配策略,动态调整以适应应用需求。对于高内存消耗任务,AQ预留足够连续空间避免碎片化;引入内存压缩技术以增加可用空间;具备精准垃圾回收机制提高内存利用率;同时加强安全性与稳定性防止因内存错误导致的问题。总之,AQ通过先进技术提升了虚拟机性能与稳定性。
|
3月前
|
消息中间件 Kafka Java
Spring 框架与 Kafka 联姻,竟引发软件世界的革命风暴!事件驱动架构震撼登场!
【8月更文挑战第31天】《Spring 框架与 Kafka 集成:实现事件驱动架构》介绍如何利用 Spring 框架的强大功能与 Kafka 分布式流平台结合,构建灵活且可扩展的事件驱动系统。通过添加 Spring Kafka 依赖并配置 Kafka 连接信息,可以轻松实现消息的生产和消费。文中详细展示了如何设置 `KafkaTemplate`、`ProducerFactory` 和 `ConsumerFactory`,并通过示例代码说明了生产者发送消息及消费者接收消息的具体实现。这一组合为构建高效可靠的分布式应用程序提供了有力支持。
109 0
|
3月前
|
监控 持续交付 数据库
持续交付的软件系统架构
持续交付的软件系统架构
36 1