C++: Performance Optimization
Last updated: 2026-08-26
In lesson 48, we learned about C++17/20 new features.
Now, we'll learn about performance optimization — making C++ programs run faster.
C++'s advantage is performance, but using it well is not easy.
1. Performance Optimization Overview
(1) 1.1 Why Optimize?
Principles:
- Don't optimize prematurely (Premature Optimization)
- Measure first, then optimize
- Optimize the algorithm first, then the code
(2) 1.2 Performance Bottlenecks
| Bottleneck | Proportion |
|---|---|
| Algorithm | 70% |
| Memory access | 20% |
| Other | 10% |
Conclusion: Optimizing the algorithm is more important than optimizing the code.
2. Memory Alignment
(1) 2.1 What Is Memory Alignment?
Memory alignment means that data is stored at memory addresses that are multiples of a certain value (usually a power of 2).
Why is it important?
- Unaligned memory access is slower (or may even crash)
- CPU reads aligned memory faster
(2) 2.2 Example: Impact of memory alignment (Difficulty ⭐⭐)
▶ Example 1: Object-oriented programming demo (Difficulty ⭐)
#include <iostream>
struct BadAlignment {
char c; // 1 byte
int i; // 4 bytes (may be aligned to offset 4)
};
struct GoodAlignment {
int i; // 4 bytes
char c; // 1 byte
};
int main() {
std::cout << "Bad: " << sizeof(BadAlignment) << " bytes" << std::endl;
std::cout << "Good: " << sizeof(GoodAlignment) << " bytes" << std::endl;
return 0;
}
Output:
Bad: 8 bytes
Good: 8 bytes
Possible run result:
Bad: 8 bytes
Good: 8 bytes
💡 Tip:
- Placing larger members first can reduce padding
3. Cache Friendliness
(1) 3.1 CPU Cache
CPU cache is 100 times faster than main memory.
Optimization principles:
- Principle of locality: Access adjacent memory
- Sequential access: Faster than random access
- Avoid cache misses: Minimize access to non-contiguous memory
(2) 3.2 Example: Row-major vs column-major order (Difficulty ⭐⭐⭐)
#include <iostream>
### ▶ Example 2: STL container usage (Difficulty ⭐)
#include <vector>
#include <chrono>
int main() {
const int N = 1000;
std::vector<std::vector<int>> matrix(N, std::vector<int>(N));
// Row-major (cache friendly)
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto row_time = std::chrono::duration<double>(end - start).count();
// Column-major (cache unfriendly)
start = std::chrono::high_resolution_clock::now();
for (int j = 0; j < N; j++) {
for (int i = 0; i < N; i++) {
matrix[i][j] = 1;
}
}
end = std::chrono::high_resolution_clock::now();
auto col_time = std::chrono::duration<double>(end - start).count();
std::cout << "Row-major time: " << row_time << " seconds" << std::endl;
std::cout << "Column-major time: " << col_time << " seconds" << std::endl;
return 0;
}
Output:
Row-major time: 0.001 seconds
Column-major time: 0.003 seconds
4. Compiler Optimization
(1) 4.1 Optimization Levels
| Level | Flag | Description |
|---|---|---|
| O0 | None | No optimization (for debugging) |
| O1 | -O1 |
Basic optimization |
| O2 | -O2 |
Recommended (balanced) |
| O3 | -O3 |
Aggressive optimization |
| Os | -Os |
Optimize for size |
(2) 4.2 Example: Compiler optimization effect (Difficulty ⭐)
#include <iostream>
int main() {
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
std::cout << sum << std::endl;
return 0;
}
Output:
499999500000
Compilation:
g++ -O0 main.cpp # Slow
g++ -O2 main.cpp # Fast (compiler may compute the result directly)
5. Profiling Tools
(1) 5.1 Common Tools
| Tool | Platform | Description |
|---|---|---|
| gprof | Linux | GCC profiler |
| perf | Linux | Linux performance analysis tool |
| Valgrind | Cross-platform | Memory analysis |
| Visual Studio Profiler | Windows | Built into VS |
(2) 5.2 Example: Measuring time with chrono (Difficulty ⭐)
#include <iostream>
#include <chrono>
int main() {
auto start = std::chrono::high_resolution_clock::now();
// Code to measure
long sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration<double>(end - start).count();
std::cout << "Time elapsed: " << duration << " seconds" << std::endl;
return 0;
}
Output:
Time elapsed: 0.05 seconds
6. Optimization Techniques Summary
(1) 6.1 Code Level
| Technique | Description |
|---|---|
| Use move semantics | Reduce copies |
| Use emplace_back | Avoid temporary objects |
| Use reserve | Reduce vector reallocation |
| Use unordered_map | Hash table, O(1) lookup |
(2) 6.2 Algorithm Level
| Technique | Description |
|---|---|
| Choose the right data structure | vector vs list vs map |
| Use appropriate algorithms | sort vs partial_sort |
| Avoid unnecessary copies | Use references, move |
▶ Example 3: Pass by value vs pass by reference performance comparison (Difficulty ⭐)
#include <iostream>
#include <vector>
#include <chrono>
struct BigData {
std::vector<int> data;
BigData() : data(10000, 0) {}
};
// Pass by value (copy)
void processByValue(BigData d) {
(void)d;
}
// Pass by reference (no copy)
void processByRef(const BigData& d) {
(void)d;
}
int main() {
BigData big;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
processByValue(big);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Pass by value time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
processByRef(big);
}
end = std::chrono::high_resolution_clock::now();
std::cout << "Pass by reference time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;
return 0;
}
Output:
Pass by value time: 0.15 seconds
Pass by reference time: 0.001 seconds
❓ FAQ
📖 Summary
| Key Point | Summary |
|---|---|
| Memory alignment | Reduce padding |
| Cache friendliness | Sequential access, locality |
| Compiler optimization | -O2 recommended |
| Profiling | Measure first, then optimize |
| Optimization techniques | Move semantics, emplace_back |
📝 Exercises
-
Basic (Difficulty ⭐): Store 100,000 integers using both
vectorandlist, and compare the time difference for tail insertion and random access. -
Intermediate (Difficulty ⭐⭐): Test the performance difference between pass by value and pass by reference. Write a function that processes a large struct (containing
vector<int>(10000)), measuring the time for both value passing and const reference passing. -
Challenge (Difficulty ⭐⭐⭐): Use
std::chronohigh-precision timing to compare the performance differences between aforloop, STLfor_each, and range-basedforwhen traversing. Analyze the results.
- Compiler optimization flags: -O0/-O1/-O2/-O3/-Os
- Reduce copies: pass by reference, move semantics
- Container selection: vector has contiguous memory, cache-friendly
- Inline functions reduce function call overhead
- Profile-guided optimization: measure bottlenecks first, then optimize
Next lesson: Unit Testing (#50)