⚡ OS 07: Multicore Processors, Cache Coherence & Amdahl's Law
Targeted for: Juspay Elimination Round, Hackathon Part B System Discussion & High-Performance Computing
Key Focus: Concurrency vs True Parallelism, CPU Cache Hierarchies, False Sharing, MESI Protocol state transitions, Memory Barriers, and Amdahl's Law derivations.
🔀 1. Concurrency vs True Parallelism
CONCURRENCY (Single Core - Interleaved Execution)
Time ────────►
Core 0: [ Thread 1 ] [ Thread 2 ] [ Thread 1 ] [ Thread 2 ] [ Thread 1 ]
◄────────── Rapid Context Switching (Illusion) ─────────►
TRUE PARALLELISM (Multi-Core - Simultaneous Execution)
Time ────────►
Core 0: [════════════ Thread 1 (Continuous Execution) ════════════]
Core 1: [════════════ Thread 2 (Continuous Execution) ════════════]- Concurrency: Managing multiple computational tasks at overlapping intervals. Progress is interleaved via rapid time-slicing on a single CPU core.
- True Parallelism: Executing multiple computational tasks at the exact same physical nanosecond across distinct physical CPU cores.
💾 2. CPU Cache Hierarchy & False Sharing
┌──────────────────────────────────────────────┐
│ CORE 0 CORE 1 │
│ ┌──────────────────────┐ ┌──────────────┐ │
│ │ L1 Data (32KB, ~1ns) │ │ L1 Data │ │
│ ├──────────────────────┤ ├──────────────┤ │
│ │ L2 Cache(512KB,~4ns) │ │ L2 Cache │ │
│ └──────────┬───────────┘ └──────┬───────┘ │
│ └───────────┬──────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ L3 Cache (Shared 32MB)│ │
│ └───────────┬───────────┘ │
└─────────────────────────┼────────────────────┘
▼
┌───────────────────────────┐
│ Physical DDR RAM (~80ns) │
└───────────────────────────┘The 64-Byte Cache Line
CPUs do not load individual bytes from RAM; they load Cache Lines (fixed 64 bytes of contiguous memory).
💥 The False Sharing Performance Hazard
False Sharing occurs when two independent threads running on different CPU cores modify independent variables that happen to share the same 64-byte cache line.
Cache Line (64 Bytes) in Physical RAM:
┌────────────────────────────────────────────────────────┐
│ int a (4 bytes, modified by Core 0) │
│ int b (4 bytes, modified by Core 1) │
│ ... padding (56 bytes) │
└────────────────────────────────────────────────────────┘- Core 0 writes to
a$\implies$ Invalidates the entire 64-byte cache line in Core 1's L1 cache. - Core 1 writes to
b$\implies$ Invalidates the entire 64-byte cache line in Core 0's L1 cache. - The cache line ping-pongs continuously across the inter-core interconnect bus at DRAM speed, crippling multi-threaded throughput by up to $50\times$ without any logical race condition!
How to Fix False Sharing (C++ Alignment / Padding):
// Prevent false sharing by forcing variables into separate 64-byte cache lines
struct alignas(64) ThreadData {
int counter; // Guaranteed to occupy its own isolated 64-byte cache line
};🔄 3. Cache Coherence: The MESI Protocol
In a multicore architecture, each core has a private L1/L2 cache. If Core 0 updates variable x = 5, Core 1's local cache may still hold x = 0. The hardware MESI Protocol maintains cache coherence across the system bus.
The 4 MESI Cache Line States:
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ MESI STATES TAXONOMY │
├──────────────┬──────────────┬──────────────┬──────────────┬───────────────────────────────┤
│ State │ Valid in RAM?│ In other L1? │ Can Core Read│ Can Core Write Directly? │
├──────────────┼──────────────┼──────────────┼──────────────┼───────────────────────────────┤
│ **Modified** │ ❌ (Dirty) │ ❌ (No) │ ✅ Yes │ ✅ Yes (No bus traffic) │
│ **Exclusive**│ ✅ (Clean) │ ❌ (No) │ ✅ Yes │ ✅ Yes (Silent upgrade to M) │
│ **Shared** │ ✅ (Clean) │ ✅ (Yes) │ ✅ Yes │ ❌ No (Must issue Invalidate) │
│ **Invalid** │ ❌ (Stale) │ — │ ❌ No (Miss) │ ❌ No (Must fetch line) │
└──────────────┴──────────────┴──────────────┴──────────────┴───────────────────────────────┘stateDiagram-v2
Invalid --> Shared : Local Read (Other cache has it)
Invalid --> Exclusive : Local Read (No other cache has it)
Invalid --> Modified : Local Write (Issue Bus Read with Invalidate - RFO)
Shared --> Modified : Local Write (Issue Bus Invalidate)
Exclusive --> Modified : Local Write (Silent upgrade, no bus traffic)
Modified --> Shared : Bus Read by other core (Write back to RAM)
Shared --> Invalid : Bus Invalidate received from another core
Exclusive --> Invalid : Bus Read with Invalidate (RFO) received
Modified --> Invalid : Bus Read with Invalidate received (Write back + Invalidate)Direct Juspay Relevance:
When Thread A in Hackathon Part B sets locked[node] = uid, its core issues a Bus Invalidate (RFO - Request For Ownership) signal, forcing all other cores to transition their cache line for that node from Shared to Invalid. Subsequent reads on other cores incur an L3 / RAM fetch latency.
📐 4. Amdahl's Law: Limits of Multicore Scaling
Amdahl's Law computes the theoretical maximum speedup of a program when utilizing $s$ parallel processing cores:
$$S_{\text{latency}}(s) = \frac{1}{(1 - p) + \frac{p}{s}}$$
Where:
- $p$ = Fraction of the algorithm that is strictly parallelizable ($0 \le p \le 1$).
- $(1 - p)$ = Fraction of the algorithm that is strictly sequential (locks, single-threaded bottlenecks).
- $s$ = Number of CPU processing cores.
Speedup (S)
│
│ Maximum Theoretical Limit
4x ├──────────────────────────────────────────── (1 / (1 - p) = 4x for p = 75%)
│ . . ───────
│ . ─────
│ . ────
2x ├─────────── . ───
│ . ───
1x ├───'
└──────┬──────────┬──────────┬──────────┬──────►
1 4 8 16 Cores (s)Asymptotic Limit ($s \to \infty$):
As the number of CPU cores approaches infinity: $$\lim_{s \to \infty} S_{\text{latency}}(s) = \mathbf{\frac{1}{1 - p}}$$
🧮 Worked Numerical Example:
Suppose a payment processing engine has 75% of its code parallelizable ($p = 0.75$), while 25% is serial lock acquisition ($(1 - p) = 0.25$).
- Calculate Speedup on a 4-Core CPU ($s = 4$): $$S = \frac{1}{0.25 + \frac{0.75}{4}} = \frac{1}{0.25 + 0.1875} = \frac{1}{0.4375} \approx \mathbf{2.286\times}$$
- Calculate Speedup on a 64-Core Server ($s = 64$): $$S = \frac{1}{0.25 + \frac{0.75}{64}} = \frac{1}{0.25 + 0.0117} = \frac{1}{0.2617} \approx \mathbf{3.82\times}$$
- Calculate Maximum Possible Speedup with Infinite Cores ($s = \infty$): $$S_{\max} = \frac{1}{0.25} = \mathbf{4.00\times}$$ (Even with 10,000 cores, the maximum speedup can NEVER exceed 4x because of the 25% serial bottleneck!)
💬 5. Verbatim Interview Scripts (Say This Out Loud)
Q: "What is False Sharing and how do you diagnose and prevent it?"
Verbatim Answer:
"False Sharing is a hardware performance degradation that occurs in multicore systems when two independent threads running on different cores modify unrelated variables that reside within the same 64-byte cache line.When Core 0 writes to variable $A$, the MESI cache coherence protocol marks the entire 64-byte cache line as
Invalidin Core 1's cache. When Core 1 writes to adjacent variable $B$, it forces Core 0's cache line to invalidate. This triggers continuous, costly cache-line bouncing over the memory interconnect.We diagnose false sharing using Linux profiling tools like
perf c2c(cache-to-cache). We prevent it by padding shared structures or using C++17alignas(hardware_destructive_interference_size)/alignas(64)to ensure independent variables occupy distinct cache lines."
Q: "Explain Amdahl's Law and its implications for adding more CPU cores."
Verbatim Answer:
"Amdahl's Law states that the overall speedup of a system gained from parallel hardware is strictly bounded by the sequential, non-parallelizable fraction of the task. Mathematically, $S = \frac{1}{(1-p) + p/s}$.As cores approach infinity, speedup asymptotically caps at $\frac{1}{1-p}$. For example, if just 5% of a transaction pipeline requires sequential lock holding, the maximum possible acceleration across the entire system is strictly capped at $20\times$, regardless of whether we allocate 64 cores or 10,000 cores. This is why fine-grained, lock-free concurrency design is mandatory in high-throughput engines like Juspay."