6.2-3 局部性+内存层次

一、局部性

一个编写良好的电脑程序倾向于利用好的局部性。这指代程序会引用最近引用过的数据项附近的数据,或者这个程序会经常引用最近引用过的数据项。这种倾向,被称为局部性原则,是一个对软件及硬件系统的设计有着巨大影响的持久性的概念。

局部性通常被描述为两种不同的形式:时间局部性和空间局部性。在一个有着良好空间局部性的程序中,一个被引用过一次的内存地址倾向于在很近的未来多次被重新引用。在一个有着良好空间局部性的程序中,一个被引用过一次的内存地址附近的程序倾向于在很近的未来被引用。

大体上来说,一个有着良好局部新的程序要比差局部性程序运行的更快。所有层次的现代计算机系统,从硬件到操作系统到应用程序,都被设计为探索局部性。在硬件层次,局部性原则允许计算机设计者加速内存访问速度,这通过引入一个称为高速缓存(cache memories)的内存块实现。这个内存块中存储了最近引用过的指令和数据项。在操作系统层次,局部性原则使得使得操作系统将主存作为最近引用过的虚拟地址空间的缓存。类似的,操作系统使用主存来缓存最近与使用过的磁盘系统中的磁盘块。局部性原则在设计应用程序中也扮演了一个非常重要的角色。例如,网络浏览器通过缓存最近引用的本地磁盘文件来利用时间局部性。大容量网络服务器都会在前端磁盘持有最近引用的文件,再次请求这些文件时不需要服务器的任何干预。

1.1 程序数据的局部性

观察图6.19(a)中的示例程序,这个程序有好的局部性吗?
在这里插入图片描述

为了回答这个问题,我们需要观察每个变量的引用规律。在这个例子中,sum变量在每次循环终都被引用一次,因此对于sum变量而言有好的时间局部性。另一方面,由于sum是一个标量,对于sum而言没有空间局部性。

如图6.19(b)所示,向量v中的元素都是按序进行读取,这个顺序就是它们在内存中存储的顺序(为了方便假设数组从地址0处开始)。因此,对于变量v,函数有好的空间局部性,但是时间局部性并不好,因为每个向量元素都仅被访问了一次。由于函数在循环体内的每个变量都有时间局部性或空间局部性,因此这个sumvec函数良好的利用了局部性。

一个诸如sumvec的按顺序访问向量中的每一个元素的函数被称为有着1步引用模式(stride-1 reference pattern,1指代元素大小)。有时候1步引用模式也被称为顺序引用模式。访问一个连续向量中的每k个元素称为k步引用模式(stride-k reference pattern)。1步引用模式是计算机中常见也是中很重要的空间局部性的原因。通常来讲,随着步的提升,空间局部性减弱。

对于引用多维度的数组来说步也是一个重要因素。例如,观察图6.20(a)所示的的sumarrayrows函数,这个函数的功能是将二维数组进行加和:
在这里插入图片描述
函数内的双重循环以行优先的顺序读取数组元素。sumarrayrows函数拥有良好的空间局部性,因为它读取数组元素的顺序和数组元素按照行优先的存储顺序相同。

看起来很平常的对程序的修改可能导致对空间局部性的巨大影响。例如图6.21(a)所示的sumarraycols函数和图6.20(a)所示的sumarrayrows函数计算同样的结果,唯一的区别是先遍历列再遍历行:
在这里插入图片描述
这个程序的空间局部性就很差,结果变成了N步引用模式。

1.2 指令获取的局部性

由于程序指令存储在内存中,并且会被CPU读取,我们也可以考虑获取程序指令时的局部性。例如,图6.19中for循环中的指令被按照再内存中顺序存储的顺序执行,因此这个循环有良好的局部性。由于这个循环体执行了很多次,它同样有着很好的空间局部性。

一个区分程序数据和程序指令的一个很重要的性质就是程序指令很少在执行过程中发生改变。CPU很少覆盖或修改这些指令。

1.3 局部性总结

这一节介绍了局部性的基本概念并且指明了一些简单的辨别程序局部性的一些规则:

  • 重复引用相同变量的程序具有良好的时间局部性
  • 对于k步引用模式的程序,步越小空间局部性越好。1步引用模式的程序有着最好的空间局部性。
  • 循环在获取指令时有较好的时间局部性和空间局部性。循环体的大小越小,循环的次数越大,局部性越好

练习 6.9

图6.22中的三个函数执行相同的功能,但是它们的空间局部性不同,对它们的空间局部性排序:
在这里插入图片描述
在这里插入图片描述
答案:
在这里插入图片描述

二、内存层次

内存层次被用在所有现代计算机内存系统中。图6.23展示了一个典型的内存层次:
在这里插入图片描述
大体上来讲,存储设备在从高层次转变向低层次时会变得更慢、更便宜且更大。在最高等级(L0)是一小部分快速的CPU寄存器,CPU可以在一个时钟周期内访问它们。接下来是一个或多个大小较小的基于SRAM的高度缓存,可以在几个CPU时钟周期内访问。然后是较大的基于DRAM的主存,这部分可以在几十到几百个时钟周期内访问到。接下来是速度慢并且相对较大的局部磁盘。最终,一些系统还包含可通过网络访问的位于一些远程处理器上的磁盘。例如,分布式文件系统比如Andrew文件系统(Andrew File System,AFS)或网络文件系统(Network File System,NFS)允许一个程序访问连接到网络中的远程服务器。类似地,广域网(World Wide Web)允许程序访问存储在世界任意角落的网络服务器上的文件。

2.1 内存层次中的缓存

通常来讲,缓存(cache)是一个较小并且访问速度速度快的存储设备,这个存储设备的作用是为存储在一个较大且较慢设备上的数据作为一个暂时的存储平台。使用缓存的过程被称为缓存过程(caching)。

内存层次的核心观点是,对于每层k,更快且更小的位于层次k的存储设备都作为位于k+1层的更大且更慢的存储设备的缓存。换句话说,在内存层次中的每一层数据都会缓存来自下一层更慢的层次的数据项,直到最小缓存层次——CPU的寄存器。

图6.24展示了内存层次中的缓存的通用概念:
在这里插入图片描述
内存k+1处的存储被划分为连续的目标数据集合,称为块(blocks)。每一个块都有独一无二的地址或名字,将其和其他块进行区分。块可以是固定大小的(通常情况下)也可以是可变大小的(例如网络服务器上存储的HTML文件)。例如,图中位于k+1层的存储被划分为16个固定大小的块,编号从0-15。

类似地,k层的存储也被划分为小集合块,块大小和k+1层相同。在任意时间点,k层的缓存都包含k+1层的数据块集合的一个子集。例如,在图6.24中,k层的缓存大小为4个块,现在包含着k+1层块4、9、14和3的拷贝。

数据总是在k层和k+1层间进行复制传输,通过块大小的传输单元(transfer units)。我们需要意识到的很重要的一点是,虽然临近层次的块大小事固定不变的,其他的层次间有着不同的块大小。如图6.23所示,L1和L0间通常使用一个字大小的块。L1和L2间(L2和L3,L3和L4)通常使用8到16个字大小的块。从L4到L5间进行的数据传输通常块大小为成百上千个字节。通常来讲,在内存层次中越低的设备有着越久的访问时间,因此趋向于使用更大的传输块来摊销较长的访问时间。

2.1.1 缓存命中(cache hits)

当一个程序需要层次k+1的一个特定的目标数据d时,它首先会在层次k中寻找包含d的块。如果d碰巧缓存在参差k时,我们称这是一次缓存命中。这时程序直接从层次k中读取d。

2.1.2 缓存缺失(cache misses)

如果目标数据d没有在层次k中进行缓存,那么此时就出现了一次缓存缺失。当出现缺失时,在层次k的缓存获取在层次k+1中的缓存中包含d的数据块,并对当前层次中的一个已经存在的数据块进行覆盖。

这个覆盖已经存在数据块的过程称为数据块的替代(replacing)或者驱逐(evicting)。被替代的数据块有时被称为待替换块(victim block)。替换哪个块由缓存的替换政策(replacement policy)决定。例如,一个使用随机替换策略(random replacement policy)的缓存会选择一个随机待替换块。最近未使用(least-recently used,LRU)替换策略会替换那些最长时间未被访问的块。

在层次k从层次k+1处获取数据块后,程序可以从层次k中读取数据d。如图6.24所示,从层次k的块12中读取数据项会导致一次缓存缺失。一旦从层次k+1中将块12拷贝到层次k后,块12将会在下次被覆盖前一直保留在层次k中。

2.1.3 缓存缺失的种类

在这里插入图片描述
在这里插入图片描述

2.1.4 缓存管理

在这里插入图片描述

2.2 对于内存层次概念的总结

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java2核心技术第I卷.基础知识 目录: 译者序 前言 第1章Java程序设计概述 1.1 Java程序设计平台 1.2 Java“白皮书”的关键术语 1.2.1简单性 1.2.2面向对象 1.2. 3分布式 1. 2.4健壮性 1. 2.5安仝性 1. 2.6体系结构立 1.2.7可移植性 1.2.8解释型 1.2.9高性能 1.2.10多线程 1.2.11动态性 1. 3 Java与Internet 1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书的示例 2.1.5导航Java目录 2.2选择开发环境 2.3使用命令行工具 2.4使用集成开发环境 2.5使用文本编辑器编译并运行程序 2.6运行图形化应用程序 2.7建立并运行applet 第3章Java基本的程序设计结构 3.1一个简单的Java应用程序 3.2注释 3.3数据类型 3.3.1整型 3.3.2浮点型 3.3.3 char类型 3.3.4 boolean类型 3.4变量 3.4.1初始化变量 3.4.2常量 3.5运算符 3.5.1自增运算符与自减运算符 3.5.2关系运算符与boolean运算符 3.5.3位运算符 3.5.4数学函数与常量 3.5.5数值类型之间的转换 3.5.6强制类型转换 3.5.7括号与运算符级别 3.5.8枚举类型 3.6字符串 3.6.1代码点与代码单元 3.6.2子串 3.6.3字符串编辑 3.6.4拼接 3.6.5检测字符串是否相等 3.6.6阅读联机API文档 3.7输入输出 3.7.1读取输入 3.7.2格式化输出 3.8控制流程 3.8.1块作用域 3.8.2条件语句 3.8.3循环 3.8.4确定循环 3.8.5多重选择――switch语句 3.8.6断控制流程语句 3.9大数值 3.10数组 3.10.1 “for each”循环 3.10.2数组初始化器以及匿名数组 3.10.3数组拷贝 3.10.4命令行参数 3.10.5数组排序 3.10.6多维数组 3.10.7不规则数组 第4章对象与类 4.1面向对象程序设计概述 4.1.1 OOP词汇表 4.1.2对象 4.1.3类之间的关系 4.1.4 00P与传统的过程化程序设计技术对比 4.2使用现有类 4.2.1对象与对象变量 4.2.2 Java库的GregorianCalendar类 4.2.3更改器方法与访问器方法 4.3用户自定义类 4.3.1一个Employee类 4.3.2多个源文件的使用 4.3.3解析Employee类 4.3.4从构造器开始 5.2.2相等测试与继承 5.2.3 hashCode方法 5.2.4 toString方法 5.3泛型数组列表 5.3.1访问数组列表元素 5.3.2类型化与原始数组列表的兼容性 5.4对象包装器与自动打包 5.5反射 5.5.1 Class类 5.5.2使用反射分析类的能力 5.5.3在运行时使用反射分析对象 5.5.4使用反射编写通用的数组代码 5.5.5方法指针 5.6枚举类 5.7继承设计技巧 第6章接口与内部类 6.1接口 6.1.1接口的特性 6.1.2接口与抽象类 6.2对象克隆 6.3接口与回调 6.4内部类 6.4.1使用内部类访问对象状态 6.4.2内部类的特殊语法规则 6.4.3内部类是否实用、必要和安全 6.4.4局部内部类 6.4.5匿名内部类 6.4.6静态内部类 6.5代理 第7章图形程序设计 7.1 Swing概述 7.2创建框架 7.3框架定位 7.4在面板显示信息 7.5 2D图形 7.6颜色 7.7为文本设定特殊字体 7.8图像 第8章事件处理 8.1、事件处理基础 8.1.1实例:处理按钮点击事件 8.1. 2建议使用内部类 8.1. 3将组件变成事件监听器 8.1.4实例:改变观感 8.1.5实例:捕获窗口事件 8.2 AWT事件继承层次 8.3 AWT的语义事件和低级事件 8.4低级事件类型 8.4.1键盘事件 8.4.2鼠标事件 8.4.3焦点事件 8.5动作 8.6多点传送 8.7实现事件源 第9章swing用户界面组件 9.1模型一视图一控制器设计模式 9.2布局管理器慨述 9.2.1边界布局 9.2.2面板 9.2.3网格布局 9.3文本输入 9.3.1文本域 9.3. 2标签与标签组件 9.3 3文本域变化跟踪 9.3.4密码域 9.3.5格式化的输入域 9.3.6文本区 9.4选择组件 9.4.1复选框 9.4.2单选按钮 9.4.3边界 9.4.4组合框 9.4.5滑块 9.4.6 JSpinner组件 9.5菜单 9.5.1菜单创建 9.5.2菜单项的图标 9.5.3复选框和单选按钮菜单项 9.5.4弹出菜单 9.5.5快捷键和加速器 9.5.6启用和禁用菜单项 9.5.7工具栏 9 5.8工具提示 9.6复杂的布局管理 9.6.1箱式布局 9.6.2网格组布局 9.6.3弹簧布局 9.6.4不使用布局管理器 9.6.5定制布局管理器 9 6.6遍历顺序 9.7对话框 9.7.1选项对话框 9.7.2创建对话框 9.7.3数据交换 9.7.4文件对话框 9.7.5颜色选择器 第10章部署applet和应用程序 10.1 applet基础 10. 1. 1一个简单的applet 10.1. 1.2查看applet 10.1.3将应用程序转换为applet lO.1.4 applet的生命周期 10.1.5安全基础 10.1.6 applet的弹出式窗口 10.2 applet的HTML标记和属性 11.5.1启用和禁用断言 11.5.2使用断言的建议 11.6调试技术 11. 6.1调试的常用技巧 11.6.2使用控制台窗口 11.6_3跟踪AWT事件 11.6.4 AWT的Robot类 11.7使用调试器 11.7.1 JDB调试器 11.7.2 Eclipse调试器 第12章流与文件 12.1流 12.2完整的流结构 12.2.1流过滤器的分层 12.2.2数据流 12.2.3随机存取文件流 12.2.4文本流 12.2.5字符集 12.2.6文本输出 12.2.7文本输入 12.3 ZIP文件流 12.4流的使用 12.4.1分隔符输出 12.4.2字符串记号处理器和带分隔符的文本 12.4.3读取带允隔符的输入 12.4.4 StringBuilder类 12.4.5随机存取流 12.5对象流 12.5.1存储可变类型的对象 12.5.2理解对象序列化文件格式 12.5.3保存对象引用问题的解决 12.5.4理解对象引用的输出格式 12.5.5修改默认的序列化机制 12.5.6单元素与类型安全枚举的序列化 12.5.7版本 12.5.8使用序列化进行克隆 12.6文件管理 12.7新的I/O 12.7.1内存映射文件 12.7.2缓冲区数据结构 12.7.3文件锁定 12.8正则表达式 13章泛型程序设计 13.1为什么要使用泛型程序设计 13.2简单泛型类的定义 13.3泛型方法 13.4类型变量的限定 13.5泛型代码和虚拟机 13.5.1翻译泛型表达式 13.5.2翻译泛型方法 13.5.3调用遗留代码 13.6约束与局限性 13.6.1基本类型 13.6.2运行时类型查询 13.6.3异常 13.6.4数组 13.6 5泛型类型的实例化 13.6 6静态上下文 13.6.7擦除后的冲突 13.7泛型类型的继承规则 13.8通配符类型 13.8.1通配符的超类型限定 13.8.2无限定通配符 13.8 3通配符捕获 13.9反射和泛型 13.9.1使用Class参数进行类型匹配 13.9. 2虚拟机的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 断线程 1.3 线程状态 1.4 线程属性 1.5 同步 1.6 阻塞队列 1.7 线程安全的集合 1.8 Callable和Future 1.9 执行器 1.10 同步器 1.11 线程和Swing工作器 第2章 集合 2.1 集合接口 2.2 具体的集合 2.3 集合框架 2.4 算法 2.5 遗留下来的集合 第3章 网络 3.1 连接到服务器 3.2 实现服务器 3.3 发送E-Mail 3.4 建立URL连接 3.5 高级套接字编程 第4章 数据库编程 4.1 JDBC的设计 4.2 结构化查询语言 4.3 安装JDBC驱动程序类型 4.4 JDBC的典型用法 4.5 执行查询操作 4.6 可滚动和可更新的结集 4.7 元数据 4.8 行集 4.9 事务 4.10 高级连接管理 4.11 LDAP概述 第5章 分布式对象 5.1 客户与服务器的角色 5.2 远程方法调用 5.3 配置远程方法调用 5.4 远程方法的参数传递 5.5 服务器对象激活 5.6 JavaIDL与CORBA 5.7 远程方法调用与SOAP 第6章 高级Swing 第7章 高级AWT 第8章 JavaBean构件 第9章 安全 第10章 国际化 第11章 本地方法 第12章 XML 第13章 注释

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值