优化的分类
代码优化可以按照不同的标准进行分类,每种分类方法都有其特定的视角和应用场景。本章将详细介绍代码优化的主要分类方法,包括按平台相关性分类、按优化范围分类、按优化技术分类等,并通过示例说明各种优化类别的特点和应用场景。
1. 按平台相关性分类
1.1 机器无关优化
机器无关优化(Machine-Independent Optimization)是指不依赖于具体目标平台特性的优化技术,这些优化可以在任何平台上应用,通常在中间代码级别进行。
1.1.1 特点
- 平台无关:不依赖于特定的硬件平台或指令集
- 可移植:可以在不同的编译器和平台上重用
- 中间代码级别:通常在中间代码表示上进行
- 广泛适用:适用于各种类型的程序
1.1.2 常见技术
- 常量折叠:在编译时计算常量表达式的值
- 死代码消除:移除不会执行的代码
- 公共子表达式消除:避免重复计算相同的表达式
- 复写传播:用变量的定义值替换变量的使用
- 循环不变计算外提:将循环中不变的计算移到循环外
- 强度削弱:将昂贵的操作替换为 cheaper的操作
1.1.3 示例
// 原始代码
int foo(int x) {
int a = 10 + 20; // 常量表达式
int b = x * 2; // 可以转换为左移操作
if (false) { // 死代码
printf("This will never execute");
}
return a + b;
}
// 机器无关优化后
int foo(int x) {
int a = 30; // 常量折叠
int b = x << 1; // 强度削弱(乘法变左移)
return a + b; // 死代码已消除
}1.2 机器相关优化
机器相关优化(Machine-Dependent Optimization)是指依赖于具体目标平台特性的优化技术,这些优化针对特定的硬件平台或指令集,通常在目标代码级别进行。
1.2.1 特点
- 平台依赖:依赖于特定的硬件平台或指令集
- 针对性能:充分利用目标平台的硬件特性
- 目标代码级别:通常在目标代码表示上进行
- 效果显著:在特定平台上可以获得显著的性能提升
1.2.2 常见技术
- 指令选择:选择最优的指令序列
- 寄存器分配:优化寄存器的使用
- 指令调度:重排指令以提高执行效率
- 目标代码窥孔优化:在目标代码上进行的局部优化
- SIMD向量化:利用SIMD指令进行并行计算
- 缓存优化:优化数据访问模式以提高缓存命中率
1.2.3 示例
# x86平台上的指令选择优化
# 原始指令序列
mov eax, [ebx] # 加载内存值到EAX
add eax, [ecx] # 加内存值到EAX
mov [edx], eax # 存储结果到内存
# 优化后的指令序列(使用LEA指令)
mov eax, [ebx] # 加载内存值到EAX
lea eax, [eax+ecx] # 加内存值到EAX(使用LEA指令,避免内存访问)
mov [edx], eax # 存储结果到内存2. 按优化范围分类
2.1 局部优化
局部优化(Local Optimization)是指仅在基本块(Basic Block)内部进行的优化,基本块是指一个顺序执行的代码段,没有分支入口和分支出口(除了入口和出口)。
2.1.1 特点
- 范围局限:仅在基本块内部进行
- 分析简单:不需要考虑控制流的变化
- 实现容易:算法相对简单,容易实现
- 效果有限:优化效果相对有限,但应用广泛
2.1.2 常见技术
- 常量折叠:在编译时计算常量表达式的值
- 死代码消除:移除基本块内不会执行的代码
- 公共子表达式消除:消除基本块内的公共子表达式
- 复写传播:在基本块内传播变量的定义值
- 代数简化:简化代数表达式
- 强度削弱:将昂贵的操作替换为 cheaper的操作
2.1.3 示例
// 基本块内的局部优化
int bar(int x, int y) {
// 基本块开始
int a = x + y;
int b = x + y; // 公共子表达式
int c = a * 2; // 可以强度削弱
int d = 5 * 10; // 常量表达式
if (c > 0) { // 基本块结束(分支)
return d;
}
return a;
}
// 局部优化后
int bar(int x, int y) {
// 基本块开始
int a = x + y;
int b = a; // 公共子表达式消除
int c = a << 1; // 强度削弱(乘法变左移)
int d = 50; // 常量折叠
if (c > 0) { // 基本块结束(分支)
return d;
}
return a;
}2.2 全局优化
全局优化(Global Optimization)是指在整个函数范围内进行的优化,考虑控制流的变化和跨基本块的信息传递。
2.2.1 特点
- 范围广泛:在整个函数范围内进行
- 分析复杂:需要考虑控制流的变化
- 实现复杂:算法相对复杂,实现难度较大
- 效果显著:优化效果相对显著
2.2.2 常见技术
- 全局公共子表达式消除:消除跨基本块的公共子表达式
- 全局常量传播:在整个函数范围内传播常量值
- 全局死代码消除:移除跨基本块的死代码
- 循环优化:如循环不变计算外提、强度削弱、归纳变量消除等
- 数据流分析:如活跃变量分析、到达定值分析等
2.2.3 示例
// 全局优化示例
int baz(int n) {
int sum = 0;
int i = 0;
while (i < n) {
int a = 10 * 20; // 循环不变计算
sum += a + i; // 可以分离循环不变部分
i++; // 归纳变量
}
return sum;
}
// 全局优化后
int baz(int n) {
int sum = 0;
int i = 0;
int a = 200; // 循环不变计算外提 + 常量折叠
while (i < n) {
sum += 200 + i; // 常量传播
i++; // 归纳变量
}
return sum;
}2.3 过程间优化
过程间优化(Interprocedural Optimization)是指跨函数边界进行的优化,考虑函数调用关系和跨函数的信息传递。
2.3.1 特点
- 跨函数边界:考虑函数调用关系
- 分析复杂:需要分析整个程序的调用图
- 实现困难:算法非常复杂,实现难度大
- 效果显著:可以获得全局的优化效果
2.3.2 常见技术
- 函数内联:将函数调用替换为函数体
- 过程间常量传播:跨函数传播常量值
- 过程间死代码消除:移除跨函数的死代码
- 过程间依赖分析:分析跨函数的依赖关系
- 调用图优化:基于调用图的优化
2.3.3 示例
// 过程间优化示例
// 被调用函数
int add_one(int x) {
return x + 1;
}
// 调用函数
int calc(int y) {
int result = 0;
for (int i = 0; i < 1000; i++) {
result += add_one(y); // 函数调用
}
return result;
}
// 过程间优化后(函数内联)
int calc(int y) {
int result = 0;
for (int i = 0; i < 1000; i++) {
result += y + 1; // 内联add_one函数
}
return result;
}
// 进一步优化(循环不变计算外提)
int calc(int y) {
int result = 0;
int y_plus_1 = y + 1; // 循环不变计算外提
for (int i = 0; i < 1000; i++) {
result += y_plus_1; // 使用计算结果
}
return result;
}3. 按优化技术分类
3.1 窥孔优化
窥孔优化(Peephole Optimization)是指在一个小的指令窗口(窥孔)内进行的局部优化,通过识别和替换常见的低效指令序列来提高性能。
3.1.1 特点
- 窗口小:仅在一个小的指令窗口内进行
- 局部性:只考虑窗口内的指令
- 简单高效:算法简单,执行高效
- 广泛应用:可以在中间代码和目标代码级别应用
3.1.2 常见技术
- 冗余指令消除:移除冗余的指令
- 代数简化:简化代数表达式
- 指令合并:将多个指令合并为一个更高效的指令
- 分支优化:优化分支指令
- 存储-加载优化:优化存储和加载指令的序列
3.1.3 示例
# 窥孔优化示例(x86汇编)
# 原始指令序列
mov eax, 0
add eax, 1
# 优化后
mov eax, 1
# 原始指令序列
mov eax, [ebx]
mov [ecx], eax
# 优化后(如果ebx和ecx不重叠)
mov [ecx], [ebx]
# 原始指令序列
jmp label1
...
label1:
jmp label2
# 优化后
jmp label23.2 循环优化
循环优化(Loop Optimization)是指专门针对循环结构进行的优化,因为循环是程序性能的关键瓶颈。
3.2.1 特点
- 针对循环:专门针对循环结构
- 效果显著:可以显著提高程序性能
- 分析复杂:需要分析循环的结构和行为
- 多种技术:包含多种专门的优化技术
3.2.2 常见技术
- 循环不变计算外提:将循环中不变的计算移到循环外
- 强度削弱:将循环中的昂贵操作替换为 cheaper的操作
- 归纳变量消除:消除循环中的归纳变量
- 循环展开:展开循环以减少循环开销
- 循环合并:合并多个相邻的循环
- 循环分裂:将一个循环分裂为多个循环
- 循环交换:交换嵌套循环的顺序以提高数据局部性
- 循环融合:将多个循环融合为一个循环
3.2.3 示例
// 循环优化示例
void matrix_mult(int a[100][100], int b[100][100], int c[100][100]) {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
c[i][j] = 0;
for (int k = 0; k < 100; k++) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
// 循环交换优化(提高缓存命中率)
void matrix_mult_optimized(int a[100][100], int b[100][100], int c[100][100]) {
// 转置b矩阵以提高缓存命中率
int b_t[100][100];
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
b_t[j][i] = b[i][j];
}
}
// 使用转置后的矩阵进行计算
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
c[i][j] = 0;
for (int k = 0; k < 100; k++) {
c[i][j] += a[i][k] * b_t[j][k]; // 现在访问b_t[j][k]是连续的
}
}
}
}4. 按优化目标分类
4.1 速度优化
速度优化(Speed Optimization)是指以提高程序执行速度为主要目标的优化技术。
4.1.1 特点
- 目标明确:以提高执行速度为主要目标
- 广泛应用:适用于大多数程序
- 技术多样:包含多种提高速度的技术
4.1.2 常见技术
- 循环展开:减少循环开销
- 向量化:利用SIMD指令进行并行计算
- 函数内联:减少函数调用开销
- 寄存器分配:优化寄存器的使用
- 指令调度:提高指令级并行性
4.1.3 示例
// 速度优化示例
void compute(int* a, int* b, int* c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}
// 使用SIMD向量化优化(伪代码)
void compute_optimized(int* a, int* b, int* c, int n) {
// 向量化部分
for (int i = 0; i < n - n % 4; i += 4) {
// 使用SIMD指令一次处理4个元素
simd_add(&a[i], &b[i], &c[i]);
}
// 处理剩余元素
for (int i = n - n % 4; i < n; i++) {
c[i] = a[i] + b[i];
}
}4.2 空间优化
空间优化(Space Optimization)是指以减少程序内存使用为主要目标的优化技术。
4.2.1 特点
- 目标明确:以减少内存使用为主要目标
- 适用于资源受限环境:如嵌入式系统、移动设备等
- 技术多样:包含多种减少内存使用的技术
4.2.2 常见技术
- 常量合并:合并相同的常量值
- 数据压缩:压缩数据存储
- 变量重用:重用临时变量
- 栈帧优化:优化函数栈帧的大小
- 全局变量优化:减少全局变量的使用
4.2.3 示例
// 空间优化示例
void process_data() {
int temp1 = calculate_value();
int temp2 = process_value(temp1);
int temp3 = finalize_value(temp2);
printf("Result: %d\n", temp3);
}
// 空间优化后
void process_data_optimized() {
int temp = calculate_value();
temp = process_value(temp);
temp = finalize_value(temp);
printf("Result: %d\n", temp);
}4.3 能耗优化
能耗优化(Energy Optimization)是指以减少程序能耗为主要目标的优化技术,适用于移动设备和电池供电的系统。
4.3.1 特点
- 目标明确:以减少能耗为主要目标
- 适用于移动设备:如手机、平板电脑等
- 技术多样:包含多种减少能耗的技术
4.3.2 常见技术
- 减少内存访问:内存访问是主要的能耗来源
- 优化指令序列:减少指令执行的能耗
- 降低时钟频率:在不需要高性能时降低时钟频率
- 睡眠模式:在空闲时进入睡眠模式
4.3.3 示例
// 能耗优化示例
void process_array(int* array, int n) {
for (int i = 0; i < n; i++) {
array[i] = array[i] * 2; // 每次循环都访问内存
}
}
// 能耗优化后(减少内存访问)
void process_array_optimized(int* array, int n) {
for (int i = 0; i < n; i++) {
int value = array[i]; // 加载一次
value = value * 2; // 寄存器操作
array[i] = value; // 存储一次
}
}4.4 代码大小优化
代码大小优化(Code Size Optimization)是指以减小程序代码体积为主要目标的优化技术,适用于存储空间受限的环境。
4.4.1 特点
- 目标明确:以减小代码体积为主要目标
- 适用于存储空间受限环境:如嵌入式系统、引导加载程序等
- 技术多样:包含多种减小代码体积的技术
4.4.2 常见技术
- 函数内联控制:控制函数内联的程度
- 指令压缩:使用更紧凑的指令格式
- 代码共享:共享相似的代码片段
- 常量池:将常量集中存储
- 死代码消除:移除无用的代码
4.4.3 示例
// 代码大小优化示例
void print_message1() {
printf("Hello, world!\n");
}
void print_message2() {
printf("Hello, world!\n");
}
// 代码大小优化后
void print_message() {
printf("Hello, world!\n");
}
void print_message1() {
print_message();
}
void print_message2() {
print_message();
}5. 按优化时机分类
5.1 编译时优化
编译时优化(Compile-Time Optimization)是指在程序编译过程中进行的优化,是最常见的优化方式。
5.1.1 特点
- 在编译过程中进行:不影响程序的运行时行为
- 一次优化:编译完成后优化结果固定
- 广泛应用:是最常见的优化方式
- 实现简单:相对容易实现
5.1.2 常见技术
- 常量折叠:在编译时计算常量表达式的值
- 死代码消除:在编译时移除死代码
- 内联函数:在编译时内联函数调用
- 模板实例化:在编译时实例化模板
5.2 链接时优化
链接时优化(Link-Time Optimization,LTO)是指在程序链接过程中进行的优化,考虑整个程序的信息。
5.2.1 特点
- 在链接过程中进行:考虑整个程序的信息
- 跨模块优化:可以优化跨模块的代码
- 效果显著:可以获得全局的优化效果
- 编译时间增加:会增加链接时间
5.2.2 常见技术
- 跨模块内联:内联跨模块的函数调用
- 跨模块常量传播:在整个程序中传播常量值
- 跨模块死代码消除:移除整个程序中的死代码
- 全局变量优化:优化全局变量的使用
5.3 运行时优化
运行时优化(Runtime Optimization)是指在程序运行过程中进行的优化,如JIT编译。
5.3.1 特点
- 在运行过程中进行:可以根据运行时信息进行优化
- 动态调整:可以根据程序的运行行为调整优化策略
- 适用于解释型语言:如JavaScript、Python等
- 实现复杂:需要运行时系统的支持
5.3.2 常见技术
- JIT编译:在运行时将字节码编译为本地代码
- 自适应优化:根据程序的运行行为调整优化策略
- 轮廓引导优化:利用程序运行的轮廓信息进行优化
- 动态编译:根据运行时信息生成优化代码
6. 优化分类的应用场景
6.1 不同类型程序的优化策略
- 计算密集型程序:优先使用速度优化,如循环优化、向量化等
- 内存受限程序:优先使用空间优化,如数据压缩、变量重用等
- 移动设备程序:优先使用能耗优化,如减少内存访问、优化指令序列等
- 嵌入式系统程序:优先使用空间优化和代码大小优化
6.2 不同编译阶段的优化选择
- 前端阶段:主要进行机器无关优化,如常量折叠、死代码消除等
- 中间代码阶段:主要进行全局优化,如循环优化、数据流分析等
- 后端阶段:主要进行机器相关优化,如指令选择、寄存器分配等
- 链接阶段:主要进行过程间优化,如跨模块内联、全局变量优化等
6.3 不同优化级别的选择
- O0(无优化):不进行任何优化,编译速度快,便于调试
- O1(基本优化):进行基本的优化,如常量折叠、死代码消除等
- O2(中级优化):进行更多的优化,如循环优化、全局优化等
- O3(高级优化):进行所有可能的优化,如向量化、函数内联等
- Os(代码大小优化):优先进行代码大小优化
7. 总结
代码优化可以按照不同的标准进行分类,每种分类方法都有其特定的视角和应用场景。按平台相关性分类,代码优化可以分为机器无关优化和机器相关优化;按优化范围分类,可以分为局部优化、全局优化和过程间优化;按优化技术分类,可以分为窥孔优化和循环优化;按优化目标分类,可以分为速度优化、空间优化、能耗优化和代码大小优化;按优化时机分类,可以分为编译时优化、链接时优化和运行时优化。
不同的优化技术适用于不同的场景,编译器需要根据程序的特点和目标平台的特性选择合适的优化策略。在实际应用中,通常会组合使用多种优化技术,以获得最佳的优化效果。
通过本章的学习,我们了解了代码优化的主要分类方法及其特点,为后续学习具体的优化技术打下了基础。在后续的章节中,我们将详细介绍各种具体的优化技术,包括局部优化、循环优化、全局优化和高级优化技术。