The victim cache is a small, fully-associative cache that stores recently evicted blocks from the main cache. It acts as a buffer between the L1 cache and the next level of the memory hierarchy, reducing conflict misses and improving overall cache performance.
- Fully Associative: Any block can be placed in any location
- FIFO Replacement: Simple and effective for small caches
- Thread-Safe: Supports concurrent access in multi-threaded environments
- Comprehensive Statistics: Tracks hits, misses, and evictions
- Flexible Integration: Works with any cache level
namespace cachesim {
struct VictimBlock {
uint64_t address; // Full memory address
uint64_t tag; // Tag bits for identification
bool valid; // Valid bit
bool dirty; // Dirty bit (needs writeback)
};
} // namespace cachesim- Block Storage: Vector of VictimBlock entries
- FIFO Queue: Tracks insertion order for replacement
- Address Map: Fast lookup using hash table
- Statistics Counters: Hit/miss/eviction tracking
// Create a 4-entry victim cache
VictimCache victimCache(4);
// Configure with main cache
CacheConfig config;
config.size = 32768; // 32KB L1
config.associativity = 4;
config.blockSize = 64;
config.useVictimCache = true;
config.victimCacheSize = 4;The victim cache sits between cache levels:
CPU → L1 Cache ↔ Victim Cache → L2 Cache → Memory
On L1 eviction:
- Evicted block goes to victim cache
- If victim cache is full, oldest entry is evicted
On L1 miss:
- Check victim cache first
- If hit, swap blocks between L1 and victim cache
- If miss, fetch from L2
// In cache miss handling
if (victimCache && !l1Hit) {
// Check victim cache
auto victimBlock = victimCache->searchAndRemove(address);
if (victimBlock) {
// Hit in victim cache
hits++;
// Install in L1 (may evict another block)
auto evicted = l1Cache.installBlock(victimBlock);
// Put evicted block in victim cache
if (evicted) {
victimCache->insertBlock(*evicted);
}
return true; // Hit
}
}- Reduces conflicts in set-associative caches
- Particularly effective for direct-mapped caches
- Typical improvement: 5-25% miss rate reduction
- Extends effective cache capacity
- Helps with slightly-too-large working sets
- Minimal hardware overhead
| Configuration | Miss Rate | With Victim Cache | Improvement |
|---|---|---|---|
| Direct-mapped 4KB | 15.2% | 11.8% | 22.4% |
| 2-way 8KB | 8.7% | 7.2% | 17.2% |
| 4-way 16KB | 5.1% | 4.6% | 9.8% |
- Mutex protection for all public methods
- Lock-free statistics counters using atomics
- Safe concurrent read/write access
- Per-block overhead: ~24 bytes
- 4-entry cache: ~128 bytes total
- Negligible compared to main cache
- Victim cache hit: 2-3 cycles
- Victim cache miss: 0 cycles (parallel check)
- Block swap: 1 cycle
- Typically 2-8 entries
- Larger than 16 rarely beneficial
- Start with entries = associativity
✓ Direct-mapped or low-associativity caches ✓ Workloads with conflict misses ✓ Memory-constrained systems ✗ Already high-associativity caches ✗ Random access patterns
- Monitor victim cache hit rate
- If > 20%, consider increasing size
- If < 5%, may not be beneficial
- Check eviction rate for thrashing
VictimCache(size_t size = 4)Creates a victim cache with specified number of entries.
bool findBlock(uint64_t blockAddr) constCheck if block exists (updates hit/miss stats).
std::optional<VictimBlock> searchAndRemove(uint64_t blockAddr)Find and remove block if present.
void insertBlock(const VictimBlock& newBlock)Insert block, evicting oldest if full.
double getHitRate() const
uint64_t getHits() const
uint64_t getMisses() const
uint64_t getEvictions() constclass CacheWithVictim {
private:
Cache mainCache;
VictimCache victimCache;
public:
bool access(uint32_t address, bool isWrite) {
// Try main cache first
if (mainCache.probe(address)) {
return true; // Hit
}
// Check victim cache
auto victimHit = victimCache.searchAndRemove(address);
if (victimHit) {
// Swap with main cache
auto evicted = mainCache.installBlock(address, isWrite);
if (evicted) {
victimCache.insertBlock(evicted->toVictimBlock());
}
return true;
}
// Miss in both - fetch from next level
fetchFromNextLevel(address);
// Install in main cache
auto evicted = mainCache.installBlock(address, isWrite);
if (evicted) {
// Place evicted block in victim cache
victimCache.insertBlock(evicted->toVictimBlock());
}
return false; // Miss
}
};Effective Hit Rate = L1_Hit_Rate + (1 - L1_Hit_Rate) × Victim_Hit_Rate
Example:
- L1 hit rate: 90%
- Victim hit rate: 50%
- Effective: 90% + 10% × 50% = 95%
- Hardware cost: ~2% of L1 cache area
- Performance gain: 5-25% miss reduction
- Power overhead: Minimal (only on misses)
- Complexity: Low (fully associative is simple)
- Check for thrashing (high eviction rate)
- Verify appropriate size for workload
- Consider access pattern analysis
- May indicate capacity misses dominant
- Check if conflicts are actually occurring
- Profile miss types in main cache
- Ensure proper dirty block handling
- Verify coherence protocol compliance
- Check statistics accuracy
- Jouppi, N. P. (1990). "Improving direct-mapped cache performance by the addition of a small fully-associative cache and prefetch buffers"
- AMD Athlon processor includes 8-entry victim cache
- Intel Pentium 4 uses victim cache in L2