计算机内存架构与组织深度解析
立即解锁
发布时间: 2025-08-22 01:32:35 阅读量: 2 订阅数: 8 


编写出色代码:理解机器
### 计算机内存架构与组织深度解析
#### 1. 内存性能差异与优化策略
在计算机系统中,不同设备与内存之间的数据传输速度存在显著差异。以显卡为例,它通常通过AGP或PCI总线与CPU进行交互。PCI总线的运行频率一般为33 MHz,每个总线周期能够传输4个字节的数据。在突发模式下,视频控制器卡每秒可传输132 MB的数据,但由于技术原因,很少能达到这个速度。
与之相比,主内存通常直接连接到CPU的总线。现代CPU拥有800 - MHz、64位宽的总线,如果内存速度足够快,CPU总线理论上每秒可在内存和CPU之间传输6.4 GB的数据,这大约是PCI总线数据传输速度的48倍。
游戏程序员很早就发现,在主内存中操作屏幕数据的副本,仅在垂直回扫(大约每秒60次)时将数据复制到视频显示内存,这种机制比每次更改都直接写入显卡内存要快得多。
对于非统一内存访问(NUMA)设备,缓存和虚拟内存子系统的操作是透明的,但NUMA内存并非如此。因此,向NUMA设备写入数据的程序应尽可能减少访问次数,例如使用离屏位图来保存临时结果。如果在NUMA设备(如闪存卡)上存储和检索数据,则必须自行显式缓存数据。
#### 2. 考虑内存层次结构的软件编写
虽然内存层次结构通常对应用程序员是透明的,但了解内存性能行为的软件比不了解的软件运行速度要快得多。即使系统的缓存和分页功能对于典型程序可能表现良好,但有些软件在没有缓存系统的情况下运行速度可能更快。优秀的软件应充分利用内存层次结构。
下面是一个糟糕设计的经典示例,用于初始化二维整数数组的循环:
```c
int array[256][256];
...
for( i=0; i<256; ++i )
for( j=0; j<256; ++j )
array[j][i] = i*j;
```
令人惊讶的是,这段代码在现代CPU上的运行速度比下面的代码慢得多:
```c
int array[256][256];
...
for( i=0; i<256; ++i )
for( j=0; j<256; ++j )
array[i][j] = i*j;
```
仔细观察可以发现,这两段代码的唯一区别在于访问数组元素时`i`和`j`索引的交换。这个小修改可能导致两段代码运行时间相差一个或两个数量级。
原因在于C语言在内存中使用行主序存储二维数组。第二段代码按顺序访问内存位置,具有空间局部性。而第一段代码并非按顺序访问内存位置,它按以下顺序访问数组元素:
```plaintext
array[0][0]
array[1][0]
array[2][0]
array[3][0]
...
array[254][0]
array[255][0]
array[0][1]
array[1][1]
array[2][1]
...
```
如果每个整数为4个字节,该序列将从数组基地址开始,以0、1024、2048、3072等偏移量访问双字值。很可能这段代码只会将`n`个整数加载到`n`路组关联缓存中,随后立即导致缓存抖动,因为后续的每个数组元素都必须从缓存复制到主内存,以防止数据被覆盖。
而第二段代码不会出现缓存抖动。假设缓存行为64字节,第二段代码在从主内存加载另一个缓存行并替换现有缓存行之前,会将16个整数值存储到同一缓存行中。因此,第二段代码将从内存中加载缓存行的成本分摊到16次内存访问中,而不是像第一段代码那样只分摊到一次访问中。由于这些原因,第二段代码运行速度更快。
为了最大化内存层次结构的性能,还可以采用以下变量声明技巧:
- **集中声明变量**:尝试将在同一代码序列中使用的所有变量一起声明。在大多数语言中,这将使语言翻译器为这些变量在物理相邻的内存位置分配存储空间,从而支持空间局部性和时间局部性。
- **在过程中分配局部变量**:应尝试在过程中分配局部变量,因为大多数语言在栈上分配局部存储,并且由于系统频繁引用栈,栈上的变量往往会在缓存中。
- **分开声明标量变量和数组、记录变量**:将标量变量一起声明,并与数组和记录变量分开。访问相邻的几个标量变量中的任何一个通常会迫使系统将所有相邻对象加载到缓存中。因此,每当访问一个变量时,系统通常也会将相邻变量加载到缓存中。
在编写优秀代码时,需要研究程序的内存访问模式并相应地调整应用程序。有时,通过手动优化汇编语言重写代码可能只能实现10%的性能提升,但如果修改程序的内存访问方式,性能提升一个数量级也并不罕见。
#### 3. 运行时内存组织
操作系统(如Mac OS、Linux或Windows)会将不同类型的数据放入主内存的不同区域(段)。虽然可以通过运行链接器并指定各种参数来控制内存组织,但默认情况下,Windows会按照图11 - 6所示的组织方式将典型程序加载到内存中(Linux类似,但会重新排列一些段)。
| 内存区域 | 说明 |
| ---- | ---- |
| 栈 | 用于存储自动变量、子程序参数、临时值等 |
| 堆 | 用于动态分配和释放内存 |
| 代码段 | 包含程序的机器指令 |
| 只读数据段 | 包含用户定义的只
0
0
复制全文
相关推荐










