5 Cache Optimization Techniques to Boost C++ Performance 10x
이 글의 핵심
5 cache optimization techniques to dramatically improve C++ program performance with Before/After benchmarks.
🎯 What You’ll Learn (Reading Time: 12 minutes)
TL;DR: Learn 5 cache optimization techniques to boost C++ program performance 10x. Verify immediate effects with Before/After benchmarks. What You’ll Learn:
- ✅ Perfectly understand cache-friendly code principles
- ✅ Master array traversal and struct alignment optimization
- ✅ Solve AoS vs SoA and False Sharing problems
- ✅ Verify performance improvements with real benchmarks Real-World Applications:
- 🔥 Process large datasets 10x faster
- 🔥 Improve game engine frame rates
- 🔥 Reduce real-time system response times
- 🔥 Increase server throughput Difficulty: Intermediate | Performance Gain: 10x | Benchmarks: Included
Problem: “Same logic, why 10x difference?”
Ever experienced this?
// Code A: 50ms
for (int i = 0; i < 1000; ++i) {
for (int j = 0; j < 1000; ++j) {
sum += matrix[i][j];
}
}
// Code B: 500ms (10x slower!)
for (int j = 0; j < 1000; ++j) {
for (int i = 0; i < 1000; ++i) {
sum += matrix[i][j];
}
}
Difference: Only traversal direction changed, yet 10x difference! Cause: CPU cache misses This article covers 5 immediately applicable cache optimization techniques.
Technique 1: Sequential Memory Access (Most Important!)
Principle
CPUs fetch memory in 64-byte units (cache lines). Accessing contiguous memory is fast because it’s already in cache.
Before: Many Cache Misses
int matrix[1000][1000];
// ❌ Column-major traversal (slow)
for (int col = 0; col < 1000; ++col) {
for (int row = 0; row < 1000; ++row) {
sum += matrix[row][col]; // Cache miss!
}
}
// Time: 500ms
Problem: matrix[0][0], matrix[1][0], matrix[2][0]…
→ Accessing distant memory locations → Cache misses
After: Many Cache Hits
// ✅ Row-major traversal (fast)
for (int row = 0; row < 1000; ++row) {
for (int col = 0; col < 1000; ++col) {
sum += matrix[row][col]; // Cache hit!
}
}
// Time: 50ms
Improvement: matrix[0][0], matrix[0][1], matrix[0][2]…
→ Contiguous memory access → Cache hits
Performance Gain: 10x ⚡
Technique 2: Struct Layout Optimization
Before: Cache Inefficient
struct Player {
std::string name; // 32 bytes
int health; // 4 bytes
bool isAlive; // 1 byte
double x, y; // 16 bytes
int score; // 4 bytes
}; // Total ~60 bytes (including padding)
std::vector<Player> players(10000);
// Check health of all players
for (const auto& p : players) {
if (p.health < 50) { // Load 60 bytes, use only 4
// ...
}
}
Problem:
- Need only
healthbut load entire struct (60 bytes) - Cache line waste
After: Separate Hot Data
struct PlayerHotData {
int health; // Frequently accessed
bool isAlive;
int score;
}; // 12 bytes
struct PlayerColdData {
std::string name; // Occasionally accessed
double x, y;
};
std::vector<PlayerHotData> hotData(10000);
std::vector<PlayerColdData> coldData(10000);
// Check health only (5x faster)
for (const auto& p : hotData) {
if (p.health < 50) {
// ...
}
}
Performance Gain: 5x ⚡
Technique 3: SoA (Struct of Arrays) Pattern
Essential technique for game engines and physics simulations.
Before: AoS (Array of Structs)
struct Particle {
float x, y, z; // Position
float vx, vy, vz; // Velocity
float mass;
};
std::vector<Particle> particles(100000);
// Update position only
for (auto& p : particles) {
p.x += p.vx; // Load 32 bytes, use only 8
p.y += p.vy;
p.z += p.vz;
}
// Time: 100ms
After: SoA (Struct of Arrays)
struct ParticlesSoA {
std::vector<float> x, y, z; // Position
std::vector<float> vx, vy, vz; // Velocity
std::vector<float> mass;
};
ParticlesSoA particles;
particles.x.resize(100000);
particles.y.resize(100000);
// ... resize others
// Update position only (SIMD auto-vectorization possible)
for (size_t i = 0; i < particles.x.size(); ++i) {
particles.x[i] += particles.vx[i];
particles.y[i] += particles.vy[i];
particles.z[i] += particles.vz[i];
}
// Time: 20ms
Performance Gain: 5x ⚡ Additional Benefits:
- SIMD auto-vectorization possible
- Maximum cache line efficiency
- Increased memory bandwidth utilization
Technique 4: Eliminate False Sharing
Hidden cause of performance degradation in multithreading.
Before: False Sharing Occurs
struct Counter {
int count; // 4 bytes
};
Counter counters[4]; // Located in same cache line
// 4 threads increment their own counter
std::thread threads[4];
for (int i = 0; i < 4; ++i) {
threads[i] = std::thread([&, i]() {
for (int j = 0; j < 10000000; ++j) {
counters[i].count++; // Cache line contention!
}
});
}
// Time: 2000ms
Problem:
- 4 counters located in same cache line (64 bytes)
- When one thread writes, invalidates other threads’ caches
- Cache line ping-pong occurs
After: Cache Line Alignment
struct alignas(64) Counter { // 64-byte alignment
int count;
char padding[60]; // Pad to 64 bytes
};
Counter counters[4]; // Each in different cache line
// 4 threads increment their own counter
std::thread threads[4];
for (int i = 0; i < 4; ++i) {
threads[i] = std::thread([&, i]() {
for (int j = 0; j < 10000000; ++j) {
counters[i].count++; // Independent cache lines!
}
});
}
// Time: 200ms
Performance Gain: 10x ⚡
Technique 5: Use Prefetching
Use manual prefetching when compiler can’t do it automatically.
What is Prefetching?
Technique to fetch memory into cache in advance.
#include <xmmintrin.h> // SSE
struct Node {
int data;
Node* next;
};
// Before: No prefetching
Node* current = head;
while (current) {
process(current->data);
current = current->next; // Cache miss
}
// After: Using prefetching
Node* current = head;
while (current) {
if (current->next) {
_mm_prefetch((char*)current->next, _MM_HINT_T0); // Preload
}
process(current->data);
current = current->next;
}
Performance Gain: 2-3x ⚡
Comprehensive Benchmarks
Results measured from actual projects.
Test Environment
- CPU: Intel i7-12700K
- RAM: 32GB DDR4-3200
- Compiler: GCC 11.3, -O2
Benchmark Results
| Optimization Technique | Before | After | Improvement |
|---|---|---|---|
| Sequential array access | 500ms | 50ms | 10x |
| Struct separation | 200ms | 40ms | 5x |
| SoA pattern | 100ms | 20ms | 5x |
| Eliminate False Sharing | 2000ms | 200ms | 10x |
| Prefetching | 150ms | 50ms | 3x |
When All Applied
// Before optimization: Naive implementation
struct Entity {
std::string name;
float x, y, z;
float vx, vy, vz;
int health;
};
std::vector<Entity> entities(100000);
for (auto& e : entities) {
e.x += e.vx;
e.y += e.vy;
e.z += e.vz;
}
// Time: 500ms
// After optimization: SoA + sequential access
struct EntitiesSoA {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
};
EntitiesSoA entities;
// ... resize
for (size_t i = 0; i < entities.x.size(); ++i) {
entities.x[i] += entities.vx[i];
entities.y[i] += entities.vy[i];
entities.z[i] += entities.vz[i];
}
// Time: 20ms
// Performance gain: 25x ⚡⚡⚡
Practical Application Guide
Step 1: Profiling
Find bottlenecks before optimizing.
# Measure cache misses with perf
perf stat -e cache-misses,cache-references ./your_program
# Output:
# 10,000,000 cache-misses
# 100,000,000 cache-references
# Cache miss rate: 10% (high!)
Step 2: Optimize Hotspots
Optimize most frequently executed code first.
// Profiling result: This loop takes 80% of total time
for (auto& entity : entities) {
entity.update(); // ← Optimize here!
}
Step 3: Measure and Compare
#include <chrono>
auto start = std::chrono::high_resolution_clock::now();
// Optimized code
auto end = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Time: " << ms.count() << "ms\n";
When to Use Which Technique?
| Situation | Recommended Technique | Expected Improvement |
|---|---|---|
| 2D array traversal | Row-major traversal | 5-10x |
| Large object processing | SoA pattern | 3-5x |
| Multithreaded counters | Eliminate False Sharing | 5-10x |
| Linked list | Prefetching | 2-3x |
| Struct with many fields | Hot/cold data separation | 3-5x |
Checklist
Check before performance optimization: Measurement:
- Confirmed bottleneck with profiling?
- Measured cache miss rate?
- Prepared Before/After benchmarks? Optimization:
- Is array traversal sequential?
- Is frequently used data at front?
- No False Sharing in multithreading?
- Can SoA pattern be applied? Verification:
- Actually faster?
- Code complexity appropriate?
- Maintainable?
Precautions
1. Avoid Over-Optimization
// ❌ Over-optimization (hard to read)
for (size_t i = 0; i < n; i += 8) {
// Unrolling + SIMD + prefetching...
// 100 lines of complex code
}
// ✅ Appropriate optimization (easy to read)
for (size_t i = 0; i < n; ++i) {
data[i] = process(data[i]); // Sequential access is enough
}
Principles:
- Only when measurable improvement exists
- Balance with code complexity
- Focus optimization on bottlenecks only
2. Leverage Compiler Optimization
# Optimization flags
g++ -O3 -march=native -mtune=native program.cpp
# -O3: Maximum optimization
# -march=native: CPU-specific optimization
# -mtune=native: CPU tuning
3. Platform Differences
// Cache line size may vary by platform
#ifdef __cpp_lib_hardware_interference_size
constexpr size_t cache_line_size =
std::hardware_destructive_interference_size;
#else
constexpr size_t cache_line_size = 64; // Common size
#endif
Practical Example: Game Engine Optimization
Scenario
Need to update 100,000 entities every frame (60fps).
Before: Slow Implementation
struct Entity {
std::string name;
glm::vec3 position;
glm::vec3 velocity;
glm::vec3 rotation;
int health;
bool active;
};
std::vector<Entity> entities(100000);
// Update every frame
for (auto& e : entities) {
if (e.active) {
e.position += e.velocity;
}
}
// Time: 20ms (60fps impossible!)
After: Optimized Implementation
struct EntitySystem {
std::vector<glm::vec3> positions;
std::vector<glm::vec3> velocities;
std::vector<bool> active;
// Store other data separately
};
EntitySystem entities;
entities.positions.resize(100000);
entities.velocities.resize(100000);
entities.active.resize(100000);
// Update every frame
for (size_t i = 0; i < entities.positions.size(); ++i) {
if (entities.active[i]) {
entities.positions[i] += entities.velocities[i];
}
}
// Time: 2ms (60fps possible!)
Performance Gain: 10x → 60fps achieved ⚡
Quick Reference Cheat Sheet
// 1. Sequential access
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
matrix[i][j]; // ✅ Row-major
}
}
// 2. Hot data first
struct Hot {
int frequently_used; // Front
std::string rarely_used; // Back
};
// 3. SoA pattern
struct SoA {
std::vector<float> x;
std::vector<float> y;
};
// 4. Prevent False Sharing
struct alignas(64) ThreadData {
int counter;
char padding[60];
};
// 5. Prefetching
_mm_prefetch((char*)next_data, _MM_HINT_T0);
Summary
5 Core Techniques
- Sequential Access: Row-major array traversal → 10x improvement
- Struct Separation: Hot/cold data separation → 5x improvement
- SoA Pattern: Gather same-type data → 5x improvement
- Eliminate False Sharing: Cache line alignment → 10x improvement
- Prefetching: Preload → 2-3x improvement
Application Priority
- Profiling (find bottlenecks)
- Sequential Access (easiest, most effective)
- Struct Optimization (medium difficulty)
- SoA Pattern (large-scale data)
- False Sharing (multithreading)
Practical Tips
- ✅ Repeat: Measure → Optimize → Measure
- ✅ Focus optimization on bottlenecks only
- ✅ Balance with code complexity
- ❌ Don’t optimize all code
- ❌ Don’t optimize without measurement
Learn More
- C++ Cache-Friendly Code Complete Guide - More detailed theory and examples
- C++ Memory Alignment and Padding - Memory layout optimization
- C++ Performance Optimization Complete Guide - Comprehensive optimization strategies Make your programs 10x faster with cache optimization! 🚀
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Practical C++ cache optimization guide to boost program performance 10x.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ Cache-Friendly Code — Complete Guide
- C++ 캐시 히트(Cache Hit)를 높이는 메모리 정렬과 패딩 | False Sharing 해결
- C++ Performance Optimization: Copies, Allocations, Cache,
Keywords Covered in This Article (Related Search Terms)
This article covers C++, performance-optimization, cache, memory, benchmark, practical.