在上一篇中,我们深入探讨了算法策略层面的性能优化,这是决定程序效率的根本所在。如果说算法优化是“道”,决定了性能的上限,那么今天我们要聊的,就是编程中的“术”——那些看似基础,却能在关键时候帮你从TLE边缘力挽狂澜的编译器优化与程序实现优化技巧。
在算法竞赛的世界里,常有这样的情形:同样的算法思路,别人的代码轻松AC,你的却卡在时间限制的边缘。这其中的差距,往往就藏在编译选项的选择、循环的写法、内存访问的模式,甚至是一个简单的输入输出函数里。
本篇将为你系统梳理这些“微观”优化方法,帮助你在保证代码正确性和可读性的前提下,养成良好的编程习惯,适应竞赛的编码风格,最终构建从算法设计到代码实现的完整性能优化方法论。
在算法竞赛中,算法复杂度优化永远是第一位的,编译器优化仅能作为辅助手段。通过合理选择算法策略、数据结构、减少冗余计算、优化 IO 操作,可以显著提升程序性能,避免 TLE。优化应该在保证正确性的前提下进行,过早优化是万恶之源。先写出正确清晰的代码,再针对瓶颈进行优化。
1. 先确保正确性,再优化性能
2. 优先优化算法复杂度(O(n²) → O(n log n)→ O(n) → O(log n))
3. 关注热点代码(使用性能分析工具)
4. 掌握基本优化技巧(循环、内存、I/O优化)
5. 使用-O2编译选项(大多数情况下的最佳选择)
1. 复杂度分析最重要:先确保算法时间复杂度正确
2. 优先选择标准库:STL算法通常已经过优化
3. 关注内存使用:避免不必要的内存分配
4. 避免过度优化:清晰的代码比微优化更重要
5. 测试边界情况:大数据量性能测试
1. 掌握基础算法和数据结构的标准实现
2. 学习算法复杂度分析
3. 了解计算机组成原理基础(缓存、内存层次)
4. 多做真题,分析官方题解
5. 参与模拟赛,积累实战经验
// 常用编译器优化选项
// GCC/g++:
-O0 // 不优化(默认,适合调试)
-O1 // 基础优化
-O2 // 推荐优化级别(大多数情况使用)
-O3 // 激进优化(可能增大代码体积)
-Os // 优化代码大小
-Ofast // 违反严格标准,快速数学计算
// 示例编译命令
g++-O2 -std=c++17-Wall -Wextra program.-o program
在算法竞赛中,合理使用编译器优化可以显著提升程序性能:
#pragma GCC optimize("O3,unroll-loops")// 最高级别优化+循环展开
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")// 启用现代指令集
#define MOD 100000005// 宏定义更快(但有可能误替换造成编译错误风险)
constint MOD =1e8+5;// 编译器能优化常量计算
constint inv2 =500000004;// 2的模逆元
// 常量表达式
constexprint MAXN =100000000;
// 关闭同步,使用快速IO
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 使用C风格的scanfprintf
scanf("%d",&n);
printf("%d\n", n);
使用 scanf/printf 替代 cin/cout:在大量输入输出时效率更高。
// 快速读入(整数)
inlineintread(){
int x =0, f =1;
char ch =getchar();
while(ch <'0'|| ch >'9'){
if(ch =='-') f =-1;
ch =getchar();
}
while(ch >='0'&& ch <='9'){
x = x *10+(ch -'0');
ch =getchar();
}
return x * f;
}
// 快速输出(整数)
inlinevoidwrite(int x){
if(x <0){
putchar('-');
x =-x;
}
if(x >9)write(x /10);
putchar(x %10+'0');
}
1. 减少分支预测失败
// 不好
if(a < b){
// 路径1
}else{
// 路径2
}
把else改为if(a>=b),或把符合多的条件放在上边,也会好很多。
2. 使用无分支代码
// 不好 分支可能影响性能
if(a > b){...}
// 优化使用无分支代码
result =(a > b)* x +(a <= b)* y;
1. 减少循环内计算
// 不好
for(int i =0; i < n; i++){
arr[i]= i *expensive_function();// 重复计算
}
// 优化
int temp =expensive_function();
for(int i =0; i < n; i++){
arr[i]= i * temp;
}
2. 减少循环条件计算
// 不好
for(int i =0; i <strlen(s); i++){// 每次循环都计算strlen
// ...
}
// 优化
int len =strlen(s);
for(int i =0; i < len; i++){
// ...
}
3. 循环展开
编译器通常自动优化
// 原始循环
for(int i =0; i < n; i++){
sum += arr[i];
}
// 循环展开
for(int i =0; i < n; i +=4){
sum += a[i]+ a[i+1]+ a[i+2]+ a[i+3];
}
1. 使用内联函数
编译器通常自动优化
inlineintmax(int a,int b){
return a > b ? a : b;
}
2. 减少函数调用开销
// 不好 频繁调用小函数
for(int i =0; i < n; i++){
result +=small_calc(arr[i]);
}
// 优化 内联或直接计算
for(int i =0; i < n; i++){
result += arr[i]*2+1;// 直接计算
}
3. 用迭代替代递归
// 不好 - 递归
intfib(int n){
if(n <=1)return n;
returnfib(n-1)+fib(n-2);
}
// 优化 - 迭代
intfib(int n){
int a =0, b =1, c;
for(int i =2; i <= n; i++){
c = a + b;
a = b;
b = c;
}
return b;
}
预处理与缓存:预先计算并存储结果(如前缀和、差分、倍增)。
// 预计算常用值
constint MOD =1e9+7;
int pow2[100001];// 预计算2的幂次
pow2[0]=1;
for(int i =1; i <=100000;++i){
pow2[i]=(pow2[i-1]*2)% MOD;
}
1. 局部性原理 - 尽量顺序访问
顺序访问数组,减少缓存失效
// 不好 随机访问
for(int i =0; i < N; i++){
sum += arr[random_index[i]];
}
// 优化 顺序访问
for(int i =0; i < N; i++){
sum += arr[i];
}
// 局部性原理 - 连续访问
int sum =0;
for(int i =0; i < n;++i){
for(int j =0; j < n;++j){
sum += matrix[i][j];// 按行访问
}
}
2. 减少动态内存分配
频繁的 new/delete 或 push_back 会增加开销。
// 静态分配大数组
constint MAXN =1000000;
int data[MAXN];// 全局或静态变量
3. 使用连续内存
vector<int> vec;// 推荐:连续内存
deque<int> dq;// 不连续,随机访问慢
4. 使用局部变量缓存频繁访问的数据
int sum =0;
for(int i =0; i < n; i++){
int val = arr[i]; // 缓存值
sum += val * val;
}
1. 使用整数运算替代浮点运算
// 不好
double result = a *1.0/ b;
// 优化(如果需要精确比较)
if(a * c > b * d){// 避免除法
// ...
}
2. 减少模运算
// 不好
for(int i =0; i < n; i++){
arr[i]=(arr[i]+1)% MOD;
}
// 优化
for(int i =0; i < n; i++){
arr[i]++;
if(arr[i]>= MOD) arr[i]-= MOD;
}
// 乘2的幂
x = x << k;// x * (2^k)
// 除2的幂
x = x >> k;// x / (2^k)
// 判断2的幂
if(x &&!(x &(x-1)))// 是2的幂
// 取模优化(当除数是2的幂)
x %32=> x &31
// 判断奇偶
if(x &1)// 奇数
if(!(x &1))// 偶数
使用memset/memcpy替代循环
int arr[1000];
memset(arr,0,sizeof(arr));// 比循环更快
哈希表的平均时间复杂度为 O (1),而红黑树为 O (log n)
// 频繁查找 - unordered_map(哈希表)
unordered_map<int,int> hashMap;// O(1)查找
// 有序数据 - map(红黑树)
map<int,int> treeMap;// O(log n)查找,保持有序
constint MAXN =1000;
int hashArray[MAXN];// 直接寻址,O(1)访问
vector 和 string 的动态扩容可能导致性能损耗。
int arr[1000000];// 比vector<int>更快
1. 预分配空间
vector<int> vec;
vec.clear();
vec.reserve(1000000);// 预分配,避免多次扩容
2. 使用emplace替代push_back
vector<pair<int,int>> vec;
vec.emplace_back(1,2);// 比push_back({1,2})更快 避免临时对象构造
选择高效算法:优先使用 O (n log n) 算法而非 O (n²) 算法(如用排序替代暴力枚举)。
vector<int>prefix(n +1,0);
for(int i =1; i <= n; i++){
prefix[i]= prefix[i -1]+ arr[i -1];
}
// 求[l, r]区间和:prefix[r+1] - prefix[l]
vector<int>diff(n +2,0);
// 区间[l, r]加val
diff[l]+= val;
diff[r +1]-= val;
// 最后求前缀和得到结果
// 寻找和为target的连续子数组
int left =0, sum =0;
for(int right =0; right < n; right++){
sum += arr[right];
while(sum > target){
sum -= arr[left++];
}
if(sum == target){
// 找到解
}
}
1. 压力测试:使用最大输入规模测试程序
2. 性能分析:使用profiler找出瓶颈
2. 边界条件测试:测试最小、最大和特殊输入情况
3. 使用 -Wall -Wextra 编译选项检查潜在问题。
4. 在本地测试时,使用 clock() 或 <chrono> 测量程序运行时间。
1. 使用clock()计算耗时
#include <ctime>
clock_t start =clock();
// 关键代码
clock_t end =clock();
cout <<"Time: "<<(double)(end-start)/CLOCKS_PER_SEC <<"s\n";
2. 使用<chrono>计算耗时
#include <chrono>
#include <iostream>
// 测量代码执行时间
auto start = chrono::high_resolution_clock::now();
// 要测试的代码
// ...
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
cout <<"Time: "<< duration.count()<<"ms"<< endl;
传递大型对象时使用引用(const vector<int>& arr)而非值传递。
使用 emplace_back() 替代 push_back() 减少临时对象构造。
sort、find 等算法的时间复杂度需明确,避免误用导致 TLE
vector::insert/erase 是 O (n) 操作,避免在大数组中频繁调用。
string 的 += 操作可能触发多次内存重新分配。
// 禁止:使用线程并行
#include<thread>
voidsolve(){
thread t1(task1);// 不允许
thread t2(task2);// 不允许
}
// 禁止:内嵌汇编
asm("mov %eax, %ebx");// 不允许
// 禁止:SIMD指令集(除非题目特别允许)
#include<immintrin.h>// AVX, SSE等 - 不允许
// 禁止:自定义内存池绕过限制
void* huge_memory =malloc(10*1024*1024);// 可能超出题目限制
// 禁止:使用内存映射文件
#include<sys/mman.h>
void* addr =mmap(...);// 不允许
// 禁止:任何网络操作
#include<curl/curl.h>// 不允许
// 禁止:访问外部文件(除非题目要求)
system("cat input.txt");// 不允许
// 禁止:修改进程优先级
#include<sys/resource.h>
setpriority(PRIO_PROCESS,0,-20);// 不允许
// 竞赛常用优化模板
#include<bits/stdc++.h>
usingnamespace std;
// 取消同步(谨慎使用)
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// 宏定义简化
#definerep(i, a, b)for(int i =(a); i <(b);++i)
#defineall(x)(x).begin(),(x).end()
// 常数定义
constint INF =0x3f3f3f3f;
constdouble EPS =1e-8;
#include <iostream>
#include <vector>
usingnamespace std;
intmain(){
int n;
cin >> n;
vector<int>arr(n);
for(int i =0; i < n; i++){
cin >> arr[i];
}
// 低效操作:频繁调用vector::push_back
vector<int> result;
for(int i =0; i < n; i++){
if(arr[i]%2==0){
result.push_back(arr[i]);
}
}
// 未优化的输出
for(int x : result){
cout << x <<" ";
}
return0;
}
#include <cstdio>
#include <vector>
usingnamespace std;
intmain(){
// 关闭同步流
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
scanf("%d",&n);// 使用scanf替代cin
vector<int>arr(n);
for(int i =0; i < n; i++){
scanf("%d",&arr[i]);
}
// 预分配内存避免动态扩容
vector<int> result;
result.reserve(n);// 预先分配足够空间
for(int i =0; i < n; i++){
if(!(arr[i]&1)){// 位运算替代取模
result.push_back(arr[i]);
}
}
// 使用printf输出
for(int x : result){
printf("%d ", x);
}
return0;
}
性能优化是一门平衡的艺术,更是一场与自己代码习惯的对话。我们介绍了从编译器选项到代码实现的诸多技巧,但请始终牢记开篇的提醒:正确的算法选择永远优于局部的代码优化,代码的清晰与正确性永远是第一位的。
将这些优化技巧视为你工具箱中的备选工具——先设计出正确、清晰的解决方案,然后在必要时,有针对性地使用合适的工具进行打磨。不要为了优化而优化,更不要因为追求极致的常数优化而牺牲了代码的可读性和可维护性。
真正的编程高手,不仅知道如何写出高效的代码,更懂得在何时、何处进行优化。希望本系列文章能帮助你建立起系统的性能优化思维,在未来的竞赛和编程之路上,既能纵览全局,把握算法的“大道”,也能雕琢细节,精通实现的“微术”。优化之路,始于正确,终于高效。愿你能写出既优雅又强劲的代码。
在这个信息爆炸的时代,家长们都希望自己的孩子能够健康成长,但不少家庭...
你是否曾在计划港澳之行时,为办理港澳通行证而感到困扰?别担心,专家/...
电视机出现花屏是怎么回事?1、液晶屏故障:一般原因都是屏幕受到敲击...
怎么正确使用发光化妆镜?局部放大:利用化妆镜的放大功能仔细观察眼部...
它们在内蒙古自治区共同设立了国有地方城市商业银行。公司于2020...