循环是C++程序中最常见的控制结构之一,无论是数据处理、算法实现还是业务逻辑的编写,都离不开循环的使用。但很多看似正确的循环写法,其实存在不少可以优化的空间,不当的循环实现会导致程序运行效率大幅下降,尤其是在循环次数较多、处理数据量大的场景下,优化循环性能能带来非常明显的收益。

C++循环性能优化的常用技巧
1. 减少循环内的重复计算
很多开发者会在循环的条件判断或者循环体内重复计算不会改变的值,这会浪费大量的CPU资源。我们可以将这些重复计算的结果提前缓存到循环外部。
比如下面这段未优化的代码,每次循环都要计算vec.size()的值:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 未优化版本,每次循环都调用size()
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] << std::endl;
}
return 0;
}
优化后我们将vec.size()的结果提前存储,避免重复调用:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 优化版本,提前缓存size结果
int vec_size = vec.size();
for (int i = 0; i < vec_size; i++) {
std::cout << vec[i] << std::endl;
}
return 0;
}
2. 优化循环终止条件
循环终止条件的复杂度会直接影响每次循环的判断开销,尽量使用简单、开销小的终止条件。比如在遍历数组时,使用指针判断比使用下标判断更高效,因为指针比较不需要额外的计算。
以下是下标遍历和指针遍历的对比示例:
#include <iostream>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int len = 10;
// 下标遍历方式
for (int i = 0; i < len; i++) {
std::cout << arr[i] << std::endl;
}
// 指针遍历方式
int* p_start = arr;
int* p_end = arr + len;
for (int* p = p_start; p < p_end; p++) {
std::cout << *p << std::endl;
}
return 0;
}
3. 循环展开
循环展开是通过减少循环的迭代次数,降低循环控制的开销,从而提升性能。循环每次迭代都需要做条件判断和自增操作,展开后这些操作的次数会减少。
比如将普通的累加循环展开4次:
#include <iostream>
int main() {
int arr[100] = {0};
// 初始化数组
for (int i = 0; i < 100; i++) {
arr[i] = i;
}
int sum = 0;
int i = 0;
// 循环展开,每次处理4个元素
for (; i + 3 < 100; i += 4) {
sum += arr[i];
sum += arr[i + 1];
sum += arr[i + 2];
sum += arr[i + 3];
}
// 处理剩余不足4个的元素
for (; i < 100; i++) {
sum += arr[i];
}
std::cout << "总和是:" << sum << std::endl;
return 0;
}
4. 提升缓存友好性
CPU缓存的访问速度远快于内存,优化循环的数据访问模式,让数据尽量连续访问,能大幅提升缓存命中率,从而提升性能。比如遍历二维数组时,按行遍历比按列遍历更高效,因为二维数组在内存中是按行连续存储的。
以下是按行遍历和按列遍历的对比示例:
#include <iostream>
#include <chrono>
int main() {
const int ROW = 1000;
const int COL = 1000;
int arr[ROW][COL];
// 初始化数组
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
arr[i][j] = i + j;
}
}
// 按行遍历
auto start1 = std::chrono::high_resolution_clock::now();
int sum1 = 0;
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
sum1 += arr[i][j];
}
}
auto end1 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1);
// 按列遍历
auto start2 = std::chrono::high_resolution_clock::now();
int sum2 = 0;
for (int j = 0; j < COL; j++) {
for (int i = 0; i < ROW; i++) {
sum2 += arr[i][j];
}
}
auto end2 = std::chrono::high_resolution_clock::now();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2);
std::cout << "按行遍历耗时:" << duration1.count() << "微秒" << std::endl;
std::cout << "按列遍历耗时:" << duration2.count() << "微秒" << std::endl;
return 0;
}
5. 优化循环内的分支判断
循环内的分支判断会影响CPU的流水线执行效率,尽量减少循环内的分支,或者将分支移到循环外部。如果分支的条件在循环中不会改变,完全可以提前判断后执行不同的循环。
比如下面这段带分支的循环:
#include <iostream>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
bool flag = true;
// 未优化,分支在循环内
for (int i = 0; i < 10; i++) {
if (flag) {
std::cout << arr[i] * 2 << std::endl;
} else {
std::cout << arr[i] << std::endl;
}
}
return 0;
}
优化后将分支移到循环外部:
#include <iostream>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
bool flag = true;
// 优化后,分支在循环外
if (flag) {
for (int i = 0; i < 10; i++) {
std::cout << arr[i] * 2 << std::endl;
}
} else {
for (int i = 0; i < 10; i++) {
std::cout << arr[i] << std::endl;
}
}
return 0;
}
不同优化技巧的性能对比
我们通过一个简单的累加场景,对比不同优化方式的耗时,测试环境为普通桌面CPU,循环次数为1亿次:
| 优化方式 | 平均耗时(微秒) |
|---|---|
| 未优化(循环内重复调用size()) | 1250 |
| 提前缓存size结果 | 980 |
| 指针遍历 | 920 |
| 循环展开4次 | 760 |
| 缓存友好+循环展开 | 680 |
优化注意事项
循环优化需要结合实际场景,不要盲目优化。首先需要通过性能分析工具定位循环是否是性能瓶颈,再针对性地选择优化技巧。过度优化可能会降低代码的可读性,甚至在某些编译器开启优化选项后,手动优化反而会被编译器优化覆盖,甚至产生反效果。另外,不同的硬件架构对优化的敏感度不同,优化后最好在实际运行环境中进行测试验证。