Research-Stack/5-Applications/tools-scripts/encoding/interference_eraser_sim.py

647 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# ==============================================================================
# COPYRIGHT NO ONE EVERYWHERE LLC (WYOMING HOLDING COMPANY)
# PROJECT: SOVEREIGN STACK
# This artifact is entirely proprietary and cryptographically proven.
# Open-Source usage requires explicit permission from Brandon Scott Schneider.
# ==============================================================================
"""
Interference Eraser Cache Simulator — Tick 694
Test interference eraser principles on CPU cache optimization.
Run on your actual CPU to measure erasure vs. tracking trade-offs.
Usage:
python3 interference_eraser_cache_sim.py
# Test different erasure probabilities
python3 interference_eraser_cache_sim.py --erase_probs 0.0 0.5 0.7 0.9 1.0
# Run specific workload
python3 interference_eraser_cache_sim.py --workload spatial
"""
import numpy as np
import argparse
import time
from dataclasses import dataclass, field
from typing import Dict, List, Tuple, Optional
from collections import defaultdict
# Optional imports for visualization
try:
import matplotlib.pyplot as plt
HAS_MATPLOTLIB = True
except ImportError:
HAS_MATPLOTLIB = False
try:
import pandas as pd
HAS_PANDAS = True
except ImportError:
HAS_PANDAS = False
# ============================================================================
# Cache Models
# ============================================================================
@dataclass
class CacheLine:
"""Standard cache line with full tracking."""
tag: int
data: Optional[bytes] = None
state: str = 'I' # MESI: M/E/S/I
lru_counter: int = 0
owner_core: int = -1
access_history: List[int] = field(default_factory=list)
@dataclass
class ErasedCacheLine:
"""Interference eraser cache line with aggregate metrics only."""
tag: int
data: Optional[bytes] = None
lru_counter: int = 0 # Still need for LRU eviction
access_frequency: float = 0.0
temporal_decay: float = 0.0
spatial_cluster: int = -1
# Erased: specific core_id, specific access order
@dataclass
class CacheStats:
"""Cache performance statistics."""
hits: int = 0
misses: int = 0
evictions: int = 0
coherence_updates: int = 0
metadata_updates: int = 0
total_accesses: int = 0
@property
def hit_rate(self) -> float:
if self.total_accesses == 0:
return 0.0
return self.hits / self.total_accesses
@property
def miss_rate(self) -> float:
return 1.0 - self.hit_rate
class StandardCache:
"""
Standard CPU cache with full path tracking.
Tracks: which core, which line, access order, MESI state
"""
def __init__(self, size_mb: float = 8.0, line_size: int = 64,
associativity: int = 16, num_cores: int = 8):
self.size_bytes = int(size_mb * 1024 * 1024)
self.line_size = line_size
self.num_lines = self.size_bytes // line_size
self.associativity = associativity
self.num_sets = self.num_lines // associativity
self.num_cores = num_cores
# Cache structure: sets × ways
self.sets = [[CacheLine(tag=-1) for _ in range(associativity)]
for _ in range(self.num_sets)]
self.lru_counter = 0
self.stats = CacheStats()
def _get_set_index(self, addr: int) -> int:
return (addr // self.line_size) % self.num_sets
def _get_tag(self, addr: int) -> int:
return addr // (self.line_size * self.num_sets)
def access(self, addr: int, core_id: int, is_write: bool = False) -> bool:
"""
Access cache line. Returns True if hit, False if miss.
Tracks full path information.
"""
self.stats.total_accesses += 1
self.stats.metadata_updates += 1 # Track every access
set_idx = self._get_set_index(addr)
tag = self._get_tag(addr)
# Search for tag
for way in range(self.associativity):
line = self.sets[set_idx][way]
if line.tag == tag:
# HIT
self.stats.hits += 1
line.lru_counter = self.lru_counter
line.owner_core = core_id
line.access_history.append(core_id)
if is_write:
line.state = 'M'
self.stats.coherence_updates += 1
self.lru_counter += 1
return True
# MISS
self.stats.misses += 1
# Find LRU way
lru_way = min(range(self.associativity),
key=lambda w: self.sets[set_idx][w].lru_counter)
# Evict if necessary
if self.sets[set_idx][lru_way].tag != -1:
self.stats.evictions += 1
# Install new line
self.sets[set_idx][lru_way] = CacheLine(
tag=tag,
state='M' if is_write else 'E',
lru_counter=self.lru_counter,
owner_core=core_id,
access_history=[core_id]
)
self.lru_counter += 1
return False
class InterferenceEraserCache:
"""
Interference eraser cache with aggregate metrics.
Erases: specific core_id, specific access order
Tracks: aggregate frequency, temporal decay, spatial clustering
"""
def __init__(self, size_mb: float = 2.0, line_size: int = 64,
associativity: int = 16, num_cores: int = 8,
erase_prob: float = 0.8, tau_decay: float = 0.99):
self.size_bytes = int(size_mb * 1024 * 1024)
self.line_size = line_size
self.num_lines = self.size_bytes // line_size
self.associativity = associativity
self.num_sets = self.num_lines // associativity
self.num_cores = num_cores
self.erase_prob = erase_prob
self.tau_decay = tau_decay
# Cache structure: sets × ways
self.sets = [[ErasedCacheLine(tag=-1) for _ in range(associativity)]
for _ in range(self.num_sets)]
self.lru_counter = 0
self.stats = CacheStats()
# Spatial clustering (simple hash-based)
self.spatial_clusters = 16
def _get_set_index(self, addr: int) -> int:
return (addr // self.line_size) % self.num_sets
def _get_tag(self, addr: int) -> int:
return addr // (self.line_size * self.num_sets)
def _get_spatial_cluster(self, addr: int) -> int:
return (addr // self.line_size) % self.spatial_clusters
def access(self, addr: int, core_id: int, is_write: bool = False) -> bool:
"""
Access cache line with interference erasure.
With probability erase_prob, erase specific path information.
"""
self.stats.total_accesses += 1
set_idx = self._get_set_index(addr)
tag = self._get_tag(addr)
cluster = self._get_spatial_cluster(addr)
# Search for tag
for way in range(self.associativity):
line = self.sets[set_idx][way]
if line.tag == tag:
# HIT
self.stats.hits += 1
# Interference erasure decision
if np.random.random() < self.erase_prob:
# ERASE: Update aggregate metrics only, skip metadata counter bump
line.access_frequency += 1.0
line.temporal_decay *= self.tau_decay
line.spatial_cluster = cluster
# DO NOT update lru_counter here, this simulates lost path tracking
else:
# TRACK: Update specific metadata (standard behavior)
line.lru_counter = self.lru_counter
self.stats.metadata_updates += 1 # Heavyweight update
if is_write:
self.stats.coherence_updates += 1
self.lru_counter += 1
return True
# MISS
self.stats.misses += 1
# Find LRU way
lru_way = min(range(self.associativity),
key=lambda w: self.sets[set_idx][w].lru_counter)
# Evict if necessary
if self.sets[set_idx][lru_way].tag != -1:
self.stats.evictions += 1
# Install new line with aggregate metrics
self.sets[set_idx][lru_way] = ErasedCacheLine(
tag=tag,
lru_counter=self.lru_counter,
access_frequency=1.0,
temporal_decay=1.0,
spatial_cluster=cluster
)
self.lru_counter += 1
return False
def prefetch_decision(self, addr: int) -> Optional[int]:
"""
Waveprobe-based prefetch decision.
Uses aggregate field to decide prefetch region.
"""
set_idx = self._get_set_index(addr)
# Compute "Waveprobe response" for this set
# W_a = sum of access frequencies in set
total_freq = sum(
line.access_frequency * line.temporal_decay
for line in self.sets[set_idx]
if line.tag != -1
)
# Prefetch if aggregate response is high
if total_freq > 2.0: # Threshold
# Prefetch next spatial cluster
cluster = self._get_spatial_cluster(addr)
prefetch_addr = addr + (self.line_size * self.spatial_clusters)
return prefetch_addr
return None
# ============================================================================
# Workload Generators
# ============================================================================
def generate_spatial_workload(num_accesses: int = 10000,
stride: int = 64,
num_cores: int = 8) -> List[Tuple[int, int, bool]]:
"""
Spatial locality workload (typical array traversal).
Use limited address range to ensure cache hits.
"""
accesses = []
# Limit address range to fit in cache (8MB = 131072 lines)
addr_range = 4 * 1024 * 1024 # 4MB working set
base_addr = 0x10000000
for i in range(num_accesses):
core_id = i % num_cores
addr = base_addr + (i * stride) % addr_range
is_write = (i % 10 == 0) # 10% writes
accesses.append((addr, core_id, is_write))
return accesses
def generate_random_workload(num_accesses: int = 10000,
addr_range: int = 1024 * 1024,
num_cores: int = 8) -> List[Tuple[int, int, bool]]:
"""
Random access workload (using Zipf to ensure temporal locality where LRU matters).
"""
accesses = []
base_addr = 0x10000000
# Generate Zipf distribution (alpha=1.5)
# Range scaled so that roughly 2x the cache size is addressable
num_unique_lines = (addr_range // 64) * 4
zipf_indices = np.random.zipf(1.1, num_accesses)
# Map high indices down to our range to keep things bounded
zipf_indices = [min(idx, num_unique_lines - 1) for idx in zipf_indices]
for i in range(num_accesses):
core_id = i % num_cores
addr = base_addr + (zipf_indices[i] * 64)
is_write = (i % 10 == 0)
accesses.append((addr, core_id, is_write))
return accesses
def generate_burst_workload(num_accesses: int = 10000,
burst_size: int = 100,
num_cores: int = 8) -> List[Tuple[int, int, bool]]:
"""
Bursty workload (temporal locality).
"""
accesses = []
base_addr = 0x10000000
num_bursts = num_accesses // burst_size
for burst in range(num_bursts):
core_id = burst % num_cores
addr = base_addr + (burst * 64)
for i in range(burst_size):
is_write = (i % 10 == 0)
accesses.append((addr + (i * 64), core_id, is_write))
return accesses
def generate_multicore_workload(num_accesses: int = 10000,
shared_regions: int = 4,
num_cores: int = 8) -> List[Tuple[int, int, bool]]:
"""
Multi-core workload with shared memory regions.
"""
accesses = []
region_size = 64 * 1024 # 64KB per region
for i in range(num_accesses):
core_id = i % num_cores
region = i % shared_regions
offset = np.random.randint(0, region_size)
addr = 0x10000000 + (region * region_size) + offset
is_write = (i % 5 == 0) # 20% writes for shared regions
accesses.append((addr, core_id, is_write))
return accesses
# ============================================================================
# Benchmark Runner
# ============================================================================
@dataclass
class BenchmarkResult:
"""Results from a single benchmark run."""
cache_type: str
erase_prob: float
workload_type: str
hit_rate: float
miss_rate: float
metadata_updates: int
coherence_updates: int
total_accesses: int
elapsed_time: float
@property
def metadata_overhead(self) -> float:
return self.metadata_updates / self.total_accesses
def run_benchmark(cache, accesses: List[Tuple[int, int, bool]]) -> BenchmarkResult:
"""
Run benchmark on cache with given workload.
"""
start_time = time.perf_counter()
for addr, core_id, is_write in accesses:
cache.access(addr, core_id, is_write)
elapsed_time = time.perf_counter() - start_time
return BenchmarkResult(
cache_type=type(cache).__name__,
erase_prob=getattr(cache, 'erase_prob', 0.0),
workload_type='unknown',
hit_rate=cache.stats.hit_rate,
miss_rate=cache.stats.miss_rate,
metadata_updates=cache.stats.metadata_updates,
coherence_updates=cache.stats.coherence_updates,
total_accesses=cache.stats.total_accesses,
elapsed_time=elapsed_time
)
def compare_caches(erase_probs: List[float] = None,
workload_types: List[str] = None,
num_accesses: int = 10000,
cache_size_mb: int = 8,
num_cores: int = 8) -> List[BenchmarkResult]:
"""
Compare standard cache vs. interference eraser cache across erasure probabilities.
"""
if erase_probs is None:
erase_probs = [0.0, 0.3, 0.5, 0.7, 0.9, 1.0]
if workload_types is None:
workload_types = ['spatial', 'random', 'burst', 'multicore']
results = []
print(f"Running benchmarks: {len(erase_probs)} erasure probs × "
f"{len(workload_types)} workloads = {len(erase_probs) * len(workload_types) + len(workload_types)} runs")
print()
for workload_type in workload_types:
print(f"Workload: {workload_type}")
# Generate workload
if workload_type == 'spatial':
accesses = generate_spatial_workload(num_accesses, num_cores=num_cores)
elif workload_type == 'random':
accesses = generate_random_workload(num_accesses, num_cores=num_cores)
elif workload_type == 'burst':
accesses = generate_burst_workload(num_accesses, num_cores=num_cores)
elif workload_type == 'multicore':
accesses = generate_multicore_workload(num_accesses, num_cores=num_cores)
else:
raise ValueError(f"Unknown workload: {workload_type}")
# Standard cache (erase_prob = 0.0)
print(f" Standard cache (tracking)...")
std_cache = StandardCache(size_mb=cache_size_mb, num_cores=num_cores)
std_result = run_benchmark(std_cache, accesses)
std_result.workload_type = workload_type
results.append(std_result)
print(f" Hit rate: {std_result.hit_rate:.3f}, "
f"Metadata overhead: {std_result.metadata_overhead:.3f}")
# Interference eraser caches
for erase_prob in erase_probs:
print(f" Interference eraser cache (erase_prob={erase_prob:.1f})...")
qe_cache = InterferenceEraserCache(
size_mb=cache_size_mb,
num_cores=num_cores,
erase_prob=erase_prob
)
qe_result = run_benchmark(qe_cache, accesses)
qe_result.workload_type = workload_type
results.append(qe_result)
print(f" Hit rate: {qe_result.hit_rate:.3f}, "
f"Metadata overhead: {qe_result.metadata_overhead:.3f}")
print()
return results
# ============================================================================
# Visualization
# ============================================================================
def plot_results(results: List[BenchmarkResult], output_file: str = None):
"""
Plot benchmark results.
"""
if not HAS_MATPLOTLIB or not HAS_PANDAS:
print("Warning: matplotlib and/or pandas not available. Skipping plot.")
return
df = pd.DataFrame([r.__dict__ for r in results])
# Group by workload
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
for idx, workload in enumerate(df['workload_type'].unique()):
ax = axes[idx // 2, idx % 2]
workload_df = df[df['workload_type'] == workload]
# Plot hit rate vs erasure
ax.plot(workload_df['erase_prob'], workload_df['hit_rate'],
'o-', label='Hit Rate', linewidth=2, markersize=8)
# Plot metadata overhead
ax.plot(workload_df['erase_prob'], workload_df['metadata_overhead'],
's--', label='Metadata Overhead', linewidth=2, markersize=8)
ax.set_xlabel('Erasure Probability', fontsize=12)
ax.set_ylabel('Metric', fontsize=12)
ax.set_title(f'{workload.capitalize()} Workload', fontsize=14)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(-0.05, 1.05)
plt.tight_layout()
if output_file:
plt.savefig(output_file, dpi=150, bbox_inches='tight')
print(f"Plot saved to {output_file}")
else:
plt.show()
def print_summary(results: List[BenchmarkResult]):
"""
Print summary of key findings.
"""
if not HAS_PANDAS:
print("Warning: pandas not available. Using basic summary.")
# Basic summary without pandas
for r in results:
print(f" {r.cache_type} (erase={r.erase_prob:.1f}): "
f"hit_rate={r.hit_rate:.3f}, metadata={r.metadata_overhead:.3f}")
return
df = pd.DataFrame([r.__dict__ for r in results])
print("=" * 70)
print("INTERFERENCE ERASER CACHE BENCHMARK SUMMARY")
print("=" * 70)
print()
for workload in df['workload_type'].unique():
workload_df = df[df['workload_type'] == workload]
print(f"Workload: {workload}")
# Find optimal erasure
optimal_idx = workload_df['hit_rate'].idxmax()
optimal = workload_df.loc[optimal_idx]
standard_row = workload_df[workload_df['erase_prob'] == 0.0]
if len(standard_row) == 0:
print(f" No standard cache data found")
print()
continue
standard = standard_row.iloc[0]
print(f" Standard cache hit rate: {standard['hit_rate']:.3f}")
print(f" Optimal erasure: {optimal['erase_prob']:.1f}")
print(f" Optimal hit rate: {optimal['hit_rate']:.3f}")
print(f" Improvement: {(optimal['hit_rate'] - standard['hit_rate']) * 100:.1f}%")
if standard['metadata_overhead'] > 0:
print(f" Metadata reduction: {(1 - optimal['metadata_overhead'] / standard['metadata_overhead']) * 100:.1f}%")
print()
print("=" * 70)
# ============================================================================
# Main
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description='Interference Eraser Cache Simulator (Tick 694)'
)
parser.add_argument('--erase_probs', type=float, nargs='+',
default=[0.0, 0.3, 0.5, 0.7, 0.9, 1.0],
help='Erasure probabilities to test')
parser.add_argument('--workloads', type=str, nargs='+',
default=['spatial', 'random', 'burst', 'multicore'],
help='Workload types to test')
parser.add_argument('--num_accesses', type=int, default=10000,
help='Number of memory accesses per workload')
parser.add_argument('--cache_size', type=float, default=2.0,
help='Cache size in MB (smaller forces evictions)')
parser.add_argument('--num_cores', type=int, default=8,
help='Number of CPU cores')
parser.add_argument('--output_plot', type=str, default=None,
help='Output file for plot (PNG)')
parser.add_argument('--output_summary', type=str, default=None,
help='Output file for summary (JSON)')
args = parser.parse_args()
print("Interference Eraser Cache Simulator")
print("=" * 70)
print(f"Cache size: {args.cache_size}MB")
print(f"Cores: {args.num_cores}")
print(f"Accesses: {args.num_accesses:,}")
print(f"Erasure probs: {args.erase_probs}")
print(f"Workloads: {args.workloads}")
print("=" * 70)
print()
# Run benchmarks
results = compare_caches(
erase_probs=args.erase_probs,
workload_types=args.workloads,
num_accesses=args.num_accesses,
cache_size_mb=args.cache_size,
num_cores=args.num_cores
)
# Print summary
print_summary(results)
# Plot results
if args.output_plot:
plot_results(results, args.output_plot)
# Save summary
if args.output_summary:
import json
summary = [r.__dict__ for r in results]
with open(args.output_summary, 'w') as f:
json.dump(summary, f, indent=2)
print(f"Summary saved to {args.output_summary}")
if __name__ == '__main__':
main()