3.2. 优化技巧

3.2.1. 算法优化

3.2.1.1. 选择正确的数据结构

// 需要频繁查找:使用哈希表
std::unordered_map<int, int> fast_lookup;  // O(1) 平均

// 需要有序遍历:使用红黑树
std::map<int, int> ordered;  // O(log n)

// 需要随机访问:使用 vector
std::vector<int> random_access;  // O(1)

// 需要频繁首尾插入:使用 deque
std::deque<int> double_ended;  // O(1)

3.2.1.2. 避免不必要的工作

// 差:每次循环都计算 size()
for (size_t i = 0; i < vec.size(); ++i) { }

// 好:缓存 size
const size_t n = vec.size();
for (size_t i = 0; i < n; ++i) { }

// 更好:范围 for(编译器可优化)
for (auto& item : vec) { }

3.2.2. 内存优化

3.2.2.1. 避免不必要的拷贝

// 差:拷贝字符串
void process(std::string s) { }

// 好:const 引用
void process(const std::string& s) { }

// 更好:string_view (C++17)
void process(std::string_view s) { }

// 移动语义
std::vector<std::string> create_data() {
    std::vector<std::string> result;
    // ...
    return result;  // RVO 或移动
}

3.2.2.2. 预分配内存

std::vector<int> vec;
vec.reserve(10000);  // 预分配,避免多次重新分配

for (int i = 0; i < 10000; ++i) {
    vec.push_back(i);  // 不会触发重新分配
}

3.2.2.3. 使用 emplace

std::vector<std::pair<int, std::string>> vec;

// 差:创建临时对象再拷贝/移动
vec.push_back(std::make_pair(1, "hello"));

// 好:原地构造
vec.emplace_back(1, "hello");

3.2.2.4. 内存池

// 简单的对象池
template<typename T, size_t BlockSize = 4096>
class ObjectPool {
public:
    T* allocate() {
        if (free_list_) {
            T* result = free_list_;
            free_list_ = *reinterpret_cast<T**>(free_list_);
            return result;
        }
        if (current_block_ >= BlockSize) {
            blocks_.push_back(std::make_unique<T[]>(BlockSize));
            current_block_ = 0;
        }
        return &blocks_.back()[current_block_++];
    }

    void deallocate(T* ptr) {
        *reinterpret_cast<T**>(ptr) = free_list_;
        free_list_ = ptr;
    }

private:
    std::vector<std::unique_ptr<T[]>> blocks_;
    size_t current_block_ = BlockSize;
    T* free_list_ = nullptr;
};

3.2.3. CPU 优化

3.2.3.1. 分支预测

// 差:难以预测的分支
if (random() % 2) {
    do_something();
} else {
    do_other();
}

// 好:使用 [[likely]]/[[unlikely]] (C++20)
if (condition) [[likely]] {
    common_case();
} else [[unlikely]] {
    rare_case();
}

// 好:使用查找表代替条件
int lookup_table[] = {value_for_0, value_for_1, value_for_2, ...};
result = lookup_table[index];

3.2.3.2. 循环展开

// 手动展开
for (int i = 0; i < n; i += 4) {
    process(data[i]);
    process(data[i+1]);
    process(data[i+2]);
    process(data[i+3]);
}

// 或让编译器做(-funroll-loops)
#pragma unroll 4
for (int i = 0; i < n; ++i) {
    process(data[i]);
}

3.2.3.3. SIMD 向量化

#include <immintrin.h>

// AVX2 示例:同时处理 8 个 float
void add_arrays_simd(float* a, float* b, float* result, size_t n) {
    size_t i = 0;
    for (; i + 8 <= n; i += 8) {
        __m256 va = _mm256_loadu_ps(a + i);
        __m256 vb = _mm256_loadu_ps(b + i);
        __m256 vr = _mm256_add_ps(va, vb);
        _mm256_storeu_ps(result + i, vr);
    }
    // 处理剩余元素
    for (; i < n; ++i) {
        result[i] = a[i] + b[i];
    }
}

// 或使用自动向量化(编译器优化)
// g++ -O3 -march=native -ftree-vectorize
void add_arrays(float* __restrict a, float* __restrict b, 
                float* __restrict result, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        result[i] = a[i] + b[i];  // 编译器会向量化
    }
}

3.2.4. 缓存优化

3.2.4.1. 数据局部性

// 差:列优先访问(跳跃访问)
for (int j = 0; j < cols; ++j) {
    for (int i = 0; i < rows; ++i) {
        process(matrix[i][j]);  // 缓存不友好
    }
}

// 好:行优先访问(顺序访问)
for (int i = 0; i < rows; ++i) {
    for (int j = 0; j < cols; ++j) {
        process(matrix[i][j]);  // 缓存友好
    }
}

3.2.4.2. 结构体布局

// 差:大量填充
struct Bad {
    char a;     // 1 byte
    // 7 bytes padding
    double b;   // 8 bytes
    char c;     // 1 byte
    // 7 bytes padding
    double d;   // 8 bytes
};  // 32 bytes

// 好:紧凑布局
struct Good {
    double b;   // 8 bytes
    double d;   // 8 bytes
    char a;     // 1 byte
    char c;     // 1 byte
    // 6 bytes padding
};  // 24 bytes

3.2.4.3. 避免伪共享

// 差:不同线程修改同一缓存行的不同变量
struct Counters {
    int counter1;  // 可能在同一缓存行
    int counter2;
};

// 好:缓存行对齐
struct alignas(64) Counter {
    int value;
};

struct AlignedCounters {
    Counter counter1;
    Counter counter2;
};

3.2.5. 编译器优化提示

3.2.5.1. restrict 指针

// 告诉编译器指针不会别名
void copy(int* __restrict dest, const int* __restrict src, size_t n) {
    for (size_t i = 0; i < n; ++i) {
        dest[i] = src[i];
    }
}

3.2.5.2. 内联提示

// 建议内联
inline int fast_function(int x) { return x * 2; }

// 强制内联(编译器特定)
__attribute__((always_inline)) int very_fast(int x) { return x * 2; }

// 禁止内联
__attribute__((noinline)) void debug_function() { }

3.2.5.3. constexpr 编译期计算

// 在编译期计算
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr int result = factorial(10);  // 编译时计算

3.2.6. 常见反模式

// 反模式1:过早优化
// 不要优化未经测量的代码

// 反模式2:微优化
// 不要优化不在热点的代码

// 反模式3:破坏可读性
// 优化不应使代码难以维护

// 反模式4:假设而非测量
// 总是用 profiler 验证优化效果

警告

优化要点:

  1. 先让代码正确,再优化

  2. 用 profiler 找到热点

  3. 优化算法 > 优化代码 > 优化微指令

  4. 测量优化效果