《C程序性能优化》学习笔记【一】—— CPU与编译器概论

1.1 高速路与人行道

计算机中的程序可比作在“有红绿灯的高速路”工作。
在编写C/C++程序是,编写者会在程序中设置很多“红绿的和人行道”,导致程序减速。但是只要去掉其中几个主要障碍,程序的运行速度就会提高数十倍。

1.2 编译器是如何运作的

程序编译过程如图1-2所示。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第1张图片
GCC将程序源码编译为汇编语言程序,汇编编译器将汇编语言转换成机器语言的目标程序,链接器将目标程序和外部模块连接起来,生成可执行代码。

编译后的汇编语言程序

以程序1-1为例。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第2张图片
GCC使用-S选项生成汇编语言程序1-2(运行平台X86系列64位)
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第3张图片《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第4张图片

添加优化选项后的结果

GCC添加优化选项-O后,结果如程序1-3所示。

可以看出,生成的代码更为简练。
优化后,循环内使用寄存器代替了外部变量,在循环外将寄存器值赋值给外部变量;循环外使用裸机操作指令xorl,缩短执行时间。

1.3 CPU是如何运作的

指令集架构与微架构

CPU指令集架构是规定程序设计如何是用指令的规范,包括寻址模式和寄存器构成、中断、异常处理等。

指令集架构不同,CPU指令也会相应变化。但是,几乎所有CPU都具备一下基本指令。

  • 算术运算
  • 逻辑运算
  • 移位指令
  • 条件比较指令
  • 寄存器与内存之间的传送
  • 跳转指令

CPU执行指令集架构的方法叫做微架构

各品牌为了使CPU更高效,在微架构设计上话费大量功夫,所以即使是用有统一指令集架构的CPU,其性能和负荷方面的特点也有所不同。

如何执行指令

CPU按图1-3所示顺序执行指令。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第5张图片

  1. 读取:从内存读取指令
  2. 解码:CPU有分管上述个功能的管理单元,解码负责支配执行指令时个单元的运行。
  3. 读取数据:从寄存器或内存读取第四步执行指令是需要的数据。
  4. 执行指令:执行指令装置包含算术逻辑单元(ALU)、逻辑运算单元、乘法器、除法器、装载/记忆装置等。
  5. 输出数据

图1-4和图1-5表示了数据在寄存器和内存上时指令执行流程。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第6张图片

指令流水线

图1-6为各单元流水执行指令的过程。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第7张图片
像图中利用CPU各单元按照流水作业的方式执行指令,称之为流水线。图中4条指令仅用8个周期,性能提升很大。

实际情况复杂很多,需要考虑:

  • 不同指令花费指令周期数不同(乘除运算花费数个周期)
  • 内存读写需数十个乃至更长时钟周期

因此,内存读写频繁时,CPU性能会受到影响,因此需要引入缓存架构。

高速缓存

主存的存取速度相当慢,比CPU数十倍甚至数百倍。引入高速缓存,缓和CPU与主存间速度不匹配矛盾。CPU读取内存数据流程如图1-7所示。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第8张图片
CPU首先在缓存中查找数据,没有则读取主存上数据;读取的数据放入缓存。第二次读取的时间会大大缩短。

存储器容量越大,存取时间越多。现代计算机采用以下缓存结构:

  • 一级缓存:高速、小容量;
  • 二级缓存:比一级缓存容量稍大、存取速度稍慢;
  • 三级缓存:根据CPU确定容量

深入探讨高速缓存

高速缓存与缓存快(缓存条或缓存行)被分区管理。主存与缓存,或上下层缓存间的数据传递,以块为单位来执行。

例如,有1024个64字节的块构成的64KB的缓存。读取内存数据,可有两种贮存到缓存的映射方式。

直接映射

采用主存地址的一部分作为缓存块中的索引。将主存分为64KB大小的段,分别对应缓存中的块,映射流程如图1-8所示。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第9张图片
索引使用主存地址的一部分,如图1-9所示。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第10张图片
直接映射的方式会将间隔64KB的数据放在同一缓存块中。在有以上数据内存存取倾向的程序中,缓存块的替换会变得频繁,导致性能降低。

组相联映射(Set Associative)

准备若干个缓存块组,从索引序列号相同的缓存块中找空白块并进行分配,流程如图1-10所示。
《C程序性能优化》学习笔记【一】—— CPU与编译器概论_第11张图片
缓存中每组缓存块数目(链数、路数)需要结合缓存的命中率来设计。

缓存块的替换算法

缓存中数据随程序执行不断更新。当缓存中没有空白块时,需要置换出缓存中的数据。常用置换策略如下:

  • LRU(Least Recently Used):最近最少使用的数据被置换出缓存。需要记录缓存块的使用顺序,设计复杂。
  • PLRU(Pseudo-LRU):一段时间内未使用的缓存块作为替换候补,需要替换是将某一候补替换出去。某段时间内使用过的缓存块进行标记,置换时,未标记的某块清理出去,此简单方法可实现PLRU替换
  • FIFO(First In First Out):置换最先放入缓存的缓存块。
  • RANDOM:随机置换。

超标量指令执行

为了提高CPU高效性,增加解码器与计算器等执行装置的数量,将若干指令并行执行,是可行方法。
灵活使用次架构,首先执行非依赖指令,能达到不按顺序执行也能得到相同结果的目的。

第1章是不是偏离了主题

第1章让读者了解到计算机内部程序如何运作。就像职业赛车手想要把赛车性能发挥到极致,需要了解机械知识。

你可能感兴趣的:([软件开发]C/C++,[软件开发]性能优化)