gemm-visualizer / cache_simulator.py
Claude
Initial deployment to Hugging Face Space
6a8abde unverified
Raw History Blame Contribute Delete
6.61 kB
"""
Simple Cache Simulator for Memory Access Pattern Analysis
Simulates a simple set-associative cache to analyze cache behavior
for different GEMM loop orderings.
"""
import numpy as np
from typing import List, Tuple, Dict
from collections import OrderedDict
class CacheSimulator:
"""Simulates a simple set-associative cache."""
def __init__(self, cache_size: int = 32768, line_size: int = 64,
associativity: int = 8, element_size: int = 8):
"""
Initialize cache simulator.
Args:
cache_size: Total cache size in bytes (default: 32KB)
line_size: Cache line size in bytes (default: 64B)
associativity: Set associativity (default: 8-way)
element_size: Size of each matrix element in bytes (default: 8B for double)
"""
self.cache_size = cache_size
self.line_size = line_size
self.associativity = associativity
self.element_size = element_size
# Calculate number of sets
self.num_lines = cache_size // line_size
self.num_sets = self.num_lines // associativity
# Cache storage: dict of sets, each set is an OrderedDict (LRU)
self.cache = {i: OrderedDict() for i in range(self.num_sets)}
# Statistics
self.hits = 0
self.misses = 0
self.accesses = 0
self.hit_rate_history = []
def reset(self):
"""Reset cache and statistics."""
self.cache = {i: OrderedDict() for i in range(self.num_sets)}
self.hits = 0
self.misses = 0
self.accesses = 0
self.hit_rate_history = []
def _get_address(self, matrix_base: int, row: int, col: int, n: int) -> int:
"""
Calculate memory address for matrix[row][col].
Args:
matrix_base: Base address of the matrix
row: Row index
col: Column index
n: Matrix dimension
Returns:
Memory address
"""
# Assume row-major layout
offset = (row * n + col) * self.element_size
return matrix_base + offset
def _parse_address(self, address: int) -> Tuple[int, int, int]:
"""
Parse address into tag, set index, and block offset.
Returns:
(tag, set_index, block_offset)
"""
block_offset = address % self.line_size
set_index = (address // self.line_size) % self.num_sets
tag = address // (self.line_size * self.num_sets)
return tag, set_index, block_offset
def access(self, address: int) -> bool:
"""
Simulate a cache access.
Args:
address: Memory address to access
Returns:
True if hit, False if miss
"""
tag, set_index, _ = self._parse_address(address)
cache_set = self.cache[set_index]
self.accesses += 1
hit = False
if tag in cache_set:
# Cache hit - move to end (most recently used)
cache_set.move_to_end(tag)
self.hits += 1
hit = True
else:
# Cache miss
self.misses += 1
# Add to cache
cache_set[tag] = True
# Evict if set is full (LRU)
if len(cache_set) > self.associativity:
cache_set.popitem(last=False) # Remove least recently used
# Record hit rate periodically
if self.accesses % 100 == 0:
self.hit_rate_history.append(self.get_hit_rate())
return hit
def simulate_accesses(self, tracks: List[Tuple], matrix_size: int,
matrix_bases: Dict[str, int] = None) -> Dict:
"""
Simulate cache behavior for a sequence of memory accesses.
Args:
tracks: List of access patterns from GEMMSimulator
matrix_size: Size of matrices (n x n)
matrix_bases: Base addresses for matrices A, B, C
Returns:
Dictionary with cache statistics
"""
if matrix_bases is None:
# Default base addresses (like in the original code)
matrix_bases = {
'A': 0x10000,
'B': 0x20000,
'C': 0x30000
}
self.reset()
for a_pos, b_pos, c_pos in tracks:
# Access A[i][k]
addr_a = self._get_address(matrix_bases['A'], a_pos[0], a_pos[1], matrix_size)
self.access(addr_a)
# Access B[k][j]
addr_b = self._get_address(matrix_bases['B'], b_pos[0], b_pos[1], matrix_size)
self.access(addr_b)
# Access C[i][j] (read-modify-write, count as 2 accesses)
addr_c = self._get_address(matrix_bases['C'], c_pos[0], c_pos[1], matrix_size)
self.access(addr_c)
self.access(addr_c)
return self.get_statistics()
def get_hit_rate(self) -> float:
"""Calculate current cache hit rate."""
if self.accesses == 0:
return 0.0
return (self.hits / self.accesses) * 100
def get_statistics(self) -> Dict:
"""
Get cache statistics.
Returns:
Dictionary containing cache performance metrics
"""
return {
'total_accesses': self.accesses,
'hits': self.hits,
'misses': self.misses,
'hit_rate': self.get_hit_rate(),
'miss_rate': 100 - self.get_hit_rate(),
'hit_rate_history': self.hit_rate_history.copy(),
'cache_config': {
'cache_size': self.cache_size,
'line_size': self.line_size,
'associativity': self.associativity,
'num_sets': self.num_sets
}
}
if __name__ == '__main__':
# Test the cache simulator
from gemm_simulator import GEMMSimulator
print("Testing Cache Simulator...")
# Create GEMM simulator
gemm = GEMMSimulator(n=16, block_size=4)
# Test different loop orders
cache = CacheSimulator(cache_size=32768, line_size=64, associativity=8)
for loop_order in ['ijk', 'kji']:
tracks = gemm.simulate(loop_order, blocked=True)
stats = cache.simulate_accesses(tracks, matrix_size=16)
print(f"\n{loop_order.upper()} Loop Order:")
print(f" Total accesses: {stats['total_accesses']}")
print(f" Hits: {stats['hits']}")
print(f" Misses: {stats['misses']}")
print(f" Hit rate: {stats['hit_rate']:.2f}%")
print("\n✓ Cache Simulator module working correctly!")