优化的分类

代码优化可以按照不同的标准进行分类,每种分类方法都有其特定的视角和应用场景。本章将详细介绍代码优化的主要分类方法,包括按平台相关性分类、按优化范围分类、按优化技术分类等,并通过示例说明各种优化类别的特点和应用场景。

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 label2

3.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. 总结

代码优化可以按照不同的标准进行分类,每种分类方法都有其特定的视角和应用场景。按平台相关性分类,代码优化可以分为机器无关优化和机器相关优化;按优化范围分类,可以分为局部优化、全局优化和过程间优化;按优化技术分类,可以分为窥孔优化和循环优化;按优化目标分类,可以分为速度优化、空间优化、能耗优化和代码大小优化;按优化时机分类,可以分为编译时优化、链接时优化和运行时优化。

不同的优化技术适用于不同的场景,编译器需要根据程序的特点和目标平台的特性选择合适的优化策略。在实际应用中,通常会组合使用多种优化技术,以获得最佳的优化效果。

通过本章的学习,我们了解了代码优化的主要分类方法及其特点,为后续学习具体的优化技术打下了基础。在后续的章节中,我们将详细介绍各种具体的优化技术,包括局部优化、循环优化、全局优化和高级优化技术。

« 上一篇 代码优化概述 下一篇 » 窥孔优化