Liking cljdoc? Tell your friends :D

Phase 5: H3 Cache Inefficiency Analysis

Date: 2025-11-18 Hypothesis Tested: H3 - Cache inefficiency causes performance degradation for large inputs

Methodology

Used perf stat to measure cache behavior across all input sizes without generating flamegraphs.

Measurement Tool: Linux perf with hardware performance counters Events Measured:

  • cycles:u - CPU cycles (user-space)
  • instructions:u - Instructions executed
  • L1-dcache-loads - L1 data cache load operations
  • L1-dcache-load-misses - L1 data cache misses
  • LLC-loads - Last Level Cache (L3) load operations
  • LLC-load-misses - LLC misses

Command:

perf stat -e cycles:u,instructions:u,L1-dcache-loads,L1-dcache-load-misses,LLC-loads,LLC-load-misses \
  cargo bench --bench phonetic_rules --features phonetic-rules \
  -- "throughput_by_input_size" --output-format bencher

Results

All Input Sizes Combined (5, 10, 20, 50 phones)

Benchmark Performance: | Input Size | Time (ns/iter) | Std Dev | |------------|----------------|---------| | 5 phones | 776 | ±24 | | 10 phones | 1,718 | ±59 | | 20 phones | 5,708 | ±154 | | 50 phones | 29,045 | ±933 |

Performance Counters (across all benchmarks):

   250,665,992,879      cycles:u
   471,956,168,926      instructions:u                   #    1.88  insn per cycle
   121,257,801,299      L1-dcache-loads:u
     1,379,212,690      L1-dcache-load-misses:u          #    1.14% of all L1-dcache accesses
    31,358,391,442      L1-dcache-stores:u
       217,617,168      LLC-loads:u
         7,951,127      LLC-load-misses:u                #    3.65% of all LL-cache accesses

Key Metrics:

  • IPC (Instructions Per Cycle): 1.88
  • L1 D-cache miss rate: 1.14%
  • LLC (L3) miss rate: 3.65%

50-Phone Case (Largest Input)

Benchmark: 31,589 ns/iter (±910)

Performance Counters:

    64,071,686,699      cycles:u
   124,125,575,615      instructions:u                   #    1.94  insn per cycle
    31,752,156,374      L1-dcache-loads:u
       379,784,059      L1-dcache-load-misses:u          #    1.20% of all L1-dcache accesses
        58,996,100      LLC-loads:u
         2,911,619      LLC-load-misses:u                #    4.94% of all LL-cache accesses

Key Metrics:

  • IPC: 1.94 (excellent!)
  • L1 D-cache miss rate: 1.20% (excellent!)
  • LLC miss rate: 4.94% (excellent!)

Analysis

Cache Performance Targets (from previous optimization work)

MetricTarget (Good)MeasuredStatus
L1 D-cache miss rate<2%1.14-1.20%Excellent
LLC miss rate<10%3.65-4.94%Excellent
IPC1.5-2.51.88-1.94Excellent

Reference Baseline (from docs/optimization/substitution-set/04-h1-profiling-results.md):

  • L1 D-cache hit rate: 98.2% (1.8% miss rate)
  • LLC miss rate: 8.00%
  • IPC: 1.91

Comparison to Baseline

MetricPhonetic RulesSubstitution Set ReferenceComparison
L1 miss rate1.14-1.20%1.8%33-37% better
LLC miss rate3.65-4.94%8.00%38-54% better
IPC1.88-1.941.91~equivalent

Conclusion: Phonetic rules cache performance is better than the reference baseline from previous optimization work!


Hypothesis Validation

H3: Cache Inefficiency Causes Performance Degradation ❌ REJECTED

Evidence Against:

  1. L1 D-cache miss rate: 1.14-1.20% (well below 2% "excellent" threshold)
  2. LLC miss rate: 3.65-4.94% (well below 10% "excellent" threshold)
  3. IPC: 1.88-1.94 (above expected 1.5-2.5 range, indicating CPU-bound workload)
  4. Better than reference: Cache performance exceeds previous optimization baselines

Cache Behavior Across Input Sizes:

  • L1 miss rate remains stable (~1.14-1.20%) as input size increases from 5 to 50 phones
  • LLC miss rate increases slightly (3.65% all sizes → 4.94% for 50 phones) but remains well within excellent range
  • IPC increases with input size (1.88 all sizes → 1.94 for 50 phones), indicating better instruction-level parallelism for larger inputs

Why Cache is NOT a Bottleneck

1. Memory Access Pattern is Cache-Friendly

The code exhibits excellent locality:

  • Sequential slicing: &s[..pos], &s[(pos + pattern.len())..] access contiguous memory
  • Small working set: Phone vectors are small (5-50 bytes typically)
  • Frequent reuse: Same phonetic vector accessed multiple times during rule matching

2. Vec Operations are Optimized

The extend_from_slice() operations benefit from:

  • Prefetching: Sequential memory access triggers hardware prefetching
  • Write combining: Contiguous writes to result vector are optimized by CPU
  • L1 residency: Small vectors fit entirely in L1 cache (32 KB)

3. CPU is Compute-Bound, Not Memory-Bound

IPC of 1.88-1.94 indicates:

  • CPU is not stalled waiting for memory
  • Instruction pipeline is well-utilized
  • Memory latency is hidden by out-of-order execution

Root Cause Re-analysis

Since cache is NOT the bottleneck, the remaining source of O(n^1.5) complexity is:

Algorithmic Work = iterations × rules × n

Where:

  • Iterations: O(√n) - proven by H5 investigation
  • Rules: 8 (constant)
  • n: Input length
  • Work per position: Pattern matching + context checking (very fast, 23-27 ns)

Total Complexity: O(√n) × 8 × n = O(n^1.5)

This is fundamental algorithmic complexity, not a cache issue.


Optimization Implications

What This Means for Future Work

Cache is Healthy ✅:

  • No need for cache-focused optimizations
  • SmallVec / stack allocation unlikely to help (L1 miss rate already < 2%)
  • Memory bandwidth is not limiting factor

Focus on Algorithmic Improvements ⚡:

  • Position skipping (reduce redundant scanning)
  • Rule applicability caching (trade memory for computation)
  • Incremental rewriting (only process affected regions)

Do NOT pursue:

  • ❌ Cache-oblivious algorithms (cache is already optimal)
  • ❌ Prefetching hints (hardware prefetcher is effective)
  • ❌ Memory layout changes (current layout is cache-friendly)

Comparison: Cache vs Allocation Overhead

OptimizationOverheadStatus
H1 (Allocation)27% of total time✅ Fixed in v0.8.0
H3 (Cache)< 2% (L1 misses)✅ Already optimal

Conclusion: Allocation elimination (H1) was 13.5× more impactful than cache would be!


Hardware Context

CPU: Intel Xeon E5-2699 v3 @ 2.30GHz Cache Hierarchy:

  • L1 Data: 32 KB per core, 4-cycle latency
  • L2: 256 KB per core, 12-cycle latency
  • L3 (LLC): 45 MB shared, 42-cycle latency
  • RAM: DDR4-2133, ~200-cycle latency

Measured Latencies (from cache miss rates):

  • 98.8% of loads hit L1 (4-cycle latency)
  • ~95% of L1 misses hit LLC (42-cycle latency)
  • Only ~5% of LLC misses go to RAM (200-cycle latency)

Effective Memory Latency:

Avg latency = 0.988 × 4 + 0.012 × 0.95 × 42 + 0.012 × 0.05 × 200
            = 3.95 + 0.48 + 0.12
            = 4.55 cycles average

Cycles per load = 4.55 cycles
Time per load @ 2.3 GHz = 4.55 / 2.3e9 = 1.98 ns

Pattern Matching Time: 23-27 ns (from microbenchmarks) Memory Access Component: ~10-15% of pattern matching time

Conclusion: Even if cache were perfect (0% misses), improvement would be < 2%


Final Recommendation

H3 Verdict: ❌ REJECTED - Cache is NOT a bottleneck

Evidence:

  1. L1 miss rate: 1.14-1.20% (excellent)
  2. LLC miss rate: 3.65-4.94% (excellent)
  3. IPC: 1.88-1.94 (excellent)
  4. Better than reference baseline
  5. Memory access < 2% of total time

Impact on Investigation:

  • Eliminates cache as optimization target
  • Confirms algorithmic work is the primary cost
  • Validates focus on position skipping (Phase 3)

Documentation Status: ✅ COMPLETE Optimization Needed: ❌ NOT REQUIRED (already optimal)


References

Measurement Data:

  • /tmp/h3_cache_all.txt - All input sizes combined
  • /tmp/h3_detailed_cache.txt - Detailed cache events

Baseline Comparison:

  • docs/optimization/substitution-set/04-h1-profiling-results.md - Reference cache metrics

Related Analysis:

  • docs/optimization/phonetic/04-iteration-analysis.md - H5 (algorithmic complexity)
  • docs/optimization/phonetic/03-optimization-results.md - H1 (27% allocation overhead)

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close