Skip to content

Commit 040b911

Browse files
committed
Add Cache prediction demo
1 parent 6013229 commit 040b911

File tree

7 files changed

+229
-1
lines changed

7 files changed

+229
-1
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ version = "0.1.0"
44
edition = "2021"
55

66
[workspace]
7-
members = [ "challenges/branch-prediction", "challenges/simple-cas", "challenges/simple-parser" , "challenges/wc-command"]
7+
members = [ "challenges/branch-prediction", "challenges/cache-prediction", "challenges/simple-cas", "challenges/simple-parser" , "challenges/wc-command"]
88

99
[dependencies]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "cache-prediction"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
rust-coding-challenges = { path = "../../" }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# cache-prediction
2+
3+
## Given By
4+
5+
## Topics
6+
7+
## Main Functions
8+
9+
## Example Output
10+
11+
## Usage Example
12+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub fn solve(input: &str) -> Result<(), String> {
2+
// TODO: Implement the solution for cache-prediction
3+
Ok(())
4+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use std::time::Instant;
2+
3+
#[derive(Clone, Copy)]
4+
struct DataPoint {
5+
value: f64,
6+
timestamp: u64,
7+
category: u32,
8+
}
9+
10+
struct CachePredictionDemo {
11+
sequential_data: Vec<DataPoint>,
12+
random_data: Vec<DataPoint>,
13+
size: usize,
14+
}
15+
16+
impl CachePredictionDemo {
17+
fn new(size: usize) -> Self {
18+
let mut sequential_data: Vec<DataPoint> = (0..size)
19+
.map(|i| DataPoint {
20+
value: i as f64,
21+
timestamp: i as u64,
22+
category: i as u32 % 10,
23+
})
24+
.collect();
25+
26+
// Create random access pattern
27+
let mut random_data = sequential_data.clone();
28+
use std::collections::hash_map::DefaultHasher;
29+
use std::hash::{Hash, Hasher};
30+
31+
// Simple shuffle using hash-based swapping
32+
for i in 0..size {
33+
let mut hasher = DefaultHasher::new();
34+
i.hash(&mut hasher);
35+
let j = (hasher.finish() as usize) % size;
36+
random_data.swap(i, j);
37+
}
38+
39+
Self {
40+
sequential_data,
41+
random_data,
42+
size,
43+
}
44+
}
45+
46+
// Cache-friendly: Sequential access pattern
47+
fn process_sequential(&self) -> (f64, std::time::Duration) {
48+
let start = Instant::now();
49+
let mut sum = 0.0;
50+
51+
// Sequential access - cache prefetcher can predict next addresses
52+
for data_point in &self.sequential_data {
53+
sum += data_point.value;
54+
// Simulate some computation
55+
if data_point.category % 2 == 0 {
56+
sum += data_point.timestamp as f64 * 0.1;
57+
}
58+
}
59+
60+
let duration = start.elapsed();
61+
(sum, duration)
62+
}
63+
64+
// Cache-unfriendly: Random access pattern
65+
fn process_random(&self) -> (f64, std::time::Duration) {
66+
let start = Instant::now();
67+
let mut sum = 0.0;
68+
69+
// Random access - cache prefetcher cannot predict
70+
for data_point in &self.random_data {
71+
sum += data_point.value;
72+
// Same computation as sequential version
73+
if data_point.category % 2 == 0 {
74+
sum += data_point.timestamp as f64 * 0.1;
75+
}
76+
}
77+
78+
let duration = start.elapsed();
79+
(sum, duration)
80+
}
81+
82+
// Matrix multiplication showing cache blocking optimization
83+
fn matrix_multiply_naive(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
84+
let n = a.len();
85+
let mut c = vec![vec![0.0; n]; n];
86+
87+
// Poor cache locality: accessing B column-wise
88+
for i in 0..n {
89+
for j in 0..n {
90+
for k in 0..n {
91+
c[i][j] += a[i][k] * b[k][j]; // b[k][j] = bad cache access
92+
}
93+
}
94+
}
95+
c
96+
}
97+
98+
fn matrix_multiply_cache_friendly(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
99+
let n = a.len();
100+
let mut c = vec![vec![0.0; n]; n];
101+
102+
// Better cache locality: reorder loops to access memory sequentially
103+
for i in 0..n {
104+
for k in 0..n {
105+
for j in 0..n {
106+
c[i][j] += a[i][k] * b[k][j]; // Better: a[i][k] stays in register
107+
}
108+
}
109+
}
110+
c
111+
}
112+
113+
fn benchmark_matrix_operations(&self, size: usize) {
114+
let matrix_a: Vec<Vec<f64>> = (0..size)
115+
.map(|i| (0..size).map(|j| (i + j) as f64).collect())
116+
.collect();
117+
118+
let matrix_b: Vec<Vec<f64>> = (0..size)
119+
.map(|i| (0..size).map(|j| (i * 2 + j) as f64).collect())
120+
.collect();
121+
122+
println!("🔄 Matrix Multiplication Benchmark ({}x{}):", size, size);
123+
124+
let start = Instant::now();
125+
let _ = Self::matrix_multiply_naive(&matrix_a, &matrix_b);
126+
let naive_time = start.elapsed();
127+
128+
let start = Instant::now();
129+
let _ = Self::matrix_multiply_cache_friendly(&matrix_a, &matrix_b);
130+
let optimized_time = start.elapsed();
131+
132+
println!(" Naive approach: {:?}", naive_time);
133+
println!(" Cache-optimized: {:?}", optimized_time);
134+
println!(" Speedup: {:.2}x",
135+
naive_time.as_nanos() as f64 / optimized_time.as_nanos() as f64);
136+
}
137+
138+
fn run_benchmarks(&self) {
139+
println!("🧪 Cache Prediction Performance Analysis");
140+
println!("Dataset size: {} elements ({:.2} MB)",
141+
self.size,
142+
(self.size * std::mem::size_of::<DataPoint>()) as f64 / 1024.0 / 1024.0);
143+
144+
println!("\n📊 Memory Access Pattern Comparison:");
145+
146+
let (seq_result, seq_time) = self.process_sequential();
147+
let (rand_result, rand_time) = self.process_random();
148+
149+
println!("🔄 Sequential Access (Cache-Friendly):");
150+
println!(" Result: {:.2}", seq_result);
151+
println!(" Time: {:?}", seq_time);
152+
153+
println!("🎲 Random Access (Cache-Unfriendly):");
154+
println!(" Result: {:.2}", rand_result);
155+
println!(" Time: {:?}", rand_time);
156+
157+
let slowdown = rand_time.as_nanos() as f64 / seq_time.as_nanos() as f64;
158+
println!("📈 Performance Impact:");
159+
println!(" Random access is {:.2}x slower", slowdown);
160+
println!(" Cache prediction saves {:.1}% execution time",
161+
(1.0 - 1.0/slowdown) * 100.0);
162+
163+
// Matrix multiplication demo
164+
println!("\n");
165+
self.benchmark_matrix_operations(256);
166+
167+
println!("\n🎯 Key Insights:");
168+
println!("• Hardware prefetchers predict sequential patterns automatically");
169+
println!("• Random access patterns defeat cache prediction mechanisms");
170+
println!("• Algorithm design should consider memory access patterns");
171+
println!("• Cache-friendly code can be orders of magnitude faster");
172+
}
173+
}
174+
175+
fn main() {
176+
// Test with different sizes to see cache effects
177+
let demo = CachePredictionDemo::new(1_000_000); // ~24MB dataset
178+
demo.run_benchmarks();
179+
180+
println!("\n💡 Cache Prediction Mechanisms Explained:");
181+
println!("1. **Stride Prefetching**: Detects sequential access patterns");
182+
println!("2. **Stream Prefetching**: Identifies multiple memory streams");
183+
println!("3. **Temporal Prediction**: Recently accessed → likely to access again");
184+
println!("4. **Spatial Prediction**: Nearby addresses → likely to access soon");
185+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use super::*;
4+
use cache-prediction::solve;
5+
6+
#[test]
7+
fn test_solve() {
8+
// Call the solve function and check for expected results
9+
let result = solve();
10+
assert!(result.is_ok());
11+
// Add more assertions based on expected behavior
12+
}
13+
}

0 commit comments

Comments
 (0)