【c++ 技巧系列】提升代码性能:最佳实践与技巧
·
目录
在架构的性能优化领域中,性能优化被分为了四个层次,分别是架构级性能优化,(子)系统间性能优化,数据结构和算法性能优化以及代码级性能优化。
分层的前两级都是涉及的架构层次的优化方案,分层的后两级则是纯代码层面的优化方案,利用代码编码的最佳实践,合理的数据结构和算法以及编译器优化指令,来达到充分榨取系统性能的目的。
代码级性能优化思路
- 使用性能工具(gprof、perf、Valgrind/Callgrind、VTune),找到真正影响的瓶颈
- 优化代码不牺牲代码可读性和维护性
- 考虑平台特性,兼容性等
提高性能的代码编码最佳实践
行优先访问
// 反例
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
sum += matrix[j][i]; // 列优先访问
// 正例:缓存友好
for (int i = 0; i < M; i++)
for (int j = 0; j < N; j++)
sum += matrix[i][j]; // 行优先访问
循环展开
- 在一些场景中,对于 for 循环,有时我们并非需要逐个遍历时,可以一次遍历多个元素
for (int i = 0; i < 100; i += 4) {
data[i] = process(data[i]);
data[i+1] = process(data[i+1]);
data[i+2] = process(data[i+2]);
data[i+3] = process(data[i+3]);
}
-
优化前,每次 for 循环的时候会进行函数名的解析和查找。优化后,函数地址在循环前已经确认,相当于通过函数指针进行调用。
举例来说,现在你要通过地址簿查找老王家的地址,优化前,每次查找你都要去地址簿从头至尾找一遍。优化后,相当于你把老王家的地址记在了一个纸片片上,每次查找就是拿纸片即可。
// 优化前
for (auto& item : container) {
result += expensive_function(item);
}
// 优化后
auto func = expensive_function; // 避免多次查表
for (auto& item : container) {
result += func(item);
}
内联函数
- 短小的函数推荐使用内联
inline int square(int x) {
return x * x;
}
// 正例:由于 inline 只是对编译器的建议,若要强制,则使用 constexpr
constexpr int compile_time_square(int x) {
return x * x;
}
避免不必要的拷贝
- 通过如下等方式避免无意义的拷贝
- 引用和常量引用
- 移动语义
- std::move
- 移动构造函数和赋值运算符
- 返回值优化
- 完美转发
- string_view 和 span(c++17/20)
- 智能指针
- 就地构造
引用和常量引用
// 引用和常量引用
void process_data(const std::vector<int>& data) { // 常量引用
// 处理数据
}
移动语义
// std::move
void process() {
std::vector<std::string> data = create_strings();
std::vector<std::string> moved_data = std::move(data);
// data现在为空,moved_data接管了资源,没有拷贝
}
// 移动构造函数和赋值运算符
class MyClass {
private:
std::vector<int> data_;
public:
// 移动构造函数
MyClass(MyClass&& other) noexcept
: data_(std::move(other.data_)) {} // 移动vector,而非拷贝
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
data_ = std::move(other.data_); // 移动赋值
}
return *this;
}
};
返回值优化
// 命名返回值优化 (NRVO)
std::vector<int> create_vector() {
std::vector<int> result; // 命名的局部变量
result.push_back(1);
result.push_back(2);
return result; // 编译器优化,避免拷贝
}
// 返回值优化 (RVO)
std::string create_string() {
return std::string("hello"); // 直接构造在调用处
}
完美转发
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg)); // 保持值类别
}
// 使用
std::string str = "hello";
wrapper(str); // 传递左引用,无拷贝
wrapper("hello"); // 传递右值,无拷贝
wrapper(std::move(str)); // 传递右值引用,无拷贝
string_view 和 span(c++17/20)
// 避免字符串拷贝
void old_print(const std::string& str); // 可能产生临时string的构造
void new_print(std::string_view str); // ✓ 轻量级,无拷贝
// 避免容器拷贝
void process_array(std::span<int> data); // ✓ 视图,无拷贝
智能指针
class LargeObject {
// 大数据成员...
};
// 避免拷贝大对象 - 使用智能指针共享
std::shared_ptr<LargeObject> obj = std::make_shared<LargeObject>();
std::shared_ptr<LargeObject> obj2 = obj; // 共享所有权,无拷贝
就地构造
std::vector<std::string> vec;
// 避免临时对象拷贝
vec.push_back(std::string("hello")); // 创建临时对象,然后移动
vec.emplace_back("hello"); // ✓ 直接构造,无拷贝无移动
std::map<int, std::string> map;
map.emplace(1, "hello"); // 直接构造pair,无拷贝
分支预测优化
// 大概率发生的场景前置
if (likely_condition) { // 使用likely宏
// 主要逻辑
} else {
// 次要逻辑
}
短路径求值与提前退出
- 利用逻辑短路:&& 和 || 在满足条件时立即返回,避免后续计算
- 提前退出循环/函数:满足条件后立即返回或 break,减少不必要的执行
if (ptr != nullptr && ptr->isValid()) { /* ... */ }
预先计算和查找表
- 将计算结果提前存储在数组中,以空间换时间,避免运行时计算。记得一款古早的经典游戏就是采用的这种做法
// 优化前 - 每次都要计算sin
float slow_sin(float angle) {
return std::sin(angle);
}
// 优化后 - 使用查找表
class SinTable {
private:
static const int TABLE_SIZE = 3600; // 0.1度精度
float table[TABLE_SIZE];
public:
SinTable() {
for (int i = 0; i < TABLE_SIZE; ++i) {
table[i] = std::sin(i * M_PI / 1800.0); // 0.1度转弧度
}
}
float fast_sin(float angle_degrees) {
int index = static_cast<int>(angle_degrees * 10) % TABLE_SIZE;
if (index < 0) index += TABLE_SIZE;
return table[index];
}
};
// 使用
SinTable sin_table;
float result = sin_table.fast_sin(45.5f); // 直接查表,避免计算
内存管理优化
- 避免频繁的动态内存分配:频繁 new/delete 会带来内存碎片和性能开销,推荐使用对象池、内存池、栈上分配或预分配
// 对象池示例
ObjectPool pool;
void* obj = pool.allocate();
// 使用 obj
pool.deallocate(obj);
延迟计算
待更新
批处理操作
数据结构与算法优化
数据结构优化
- 充分了解常用容器的使用场景,合理使用这些数据结构
- std::vector:适合随机访问
- std::unordered_map:适合查找
- …
算法选择优化
// 根据数据规模选择算法
void sort_data(std::vector<int>& data) {
if (data.size() < 50) {
insertion_sort(data); // 小数据用插入排序
} else if (data.size() < 1000) {
quick_sort(data); // 中等数据用快速排序
} else {
std::sort(data.begin(), data.end()); // 大数据用内排序
}
}
编译器优化
编译器优化选项
- 使用 -O2、-O3 等编译选项:让编译器自动优化代码,如循环展开、常量折叠等
- 针对特定 CPU 优化:-march=native 等选项可生成针对本机优化的代码
g++ -O2 -o program program.cpp
编译器指令优化
// 强制内联
__attribute__((always_inline)) void critical_function() {
// 关键代码
}
// 热路径优化
__attribute__((hot)) void frequently_called_function() {
// 频繁调用的函数
}
// 冷路径优化
__attribute__((cold)) void rarely_called_function() {
// 很少调用的函数
}
内存对齐
// 结构体对齐优化
struct alignas(64) CacheLineAligned {
int data[16]; // 对齐到缓存行大小
};
// 避免false sharing
struct ThreadData {
alignas(64) int local_counter; // 每个线程独立缓存行
// ... 其他数据
};
更多推荐
所有评论(0)