1use crate::error::Result;
2use crate::graph::ArrowGraph;
3use std::collections::HashMap;
4use std::time::{Duration, Instant};
5use wide::f64x4;
6
7#[derive(Debug)]
9pub struct SimpleBenchmark {
10 pub name: String,
11 pub warmup_iterations: usize,
12 pub measurement_iterations: usize,
13}
14
15#[derive(Debug, Clone)]
17pub struct SimpleBenchmarkResult {
18 pub name: String,
19 pub avg_duration: Duration,
20 pub min_duration: Duration,
21 pub max_duration: Duration,
22 pub total_duration: Duration,
23 pub iterations: usize,
24 pub throughput_ops_per_sec: f64,
25}
26
27impl SimpleBenchmark {
28 pub fn new(name: &str) -> Self {
30 Self {
31 name: name.to_string(),
32 warmup_iterations: 3,
33 measurement_iterations: 10,
34 }
35 }
36
37 pub fn bench_pagerank(&self, graph: &ArrowGraph) -> Result<SimpleBenchmarkResult> {
39 for _ in 0..self.warmup_iterations {
41 let _ = self.run_pagerank_real(graph);
42 }
43
44 let mut durations = Vec::new();
46 let total_start = Instant::now();
47
48 for _ in 0..self.measurement_iterations {
49 let start = Instant::now();
50 let _ = self.run_pagerank_real(graph);
51 durations.push(start.elapsed());
52 }
53
54 let total_duration = total_start.elapsed();
55 self.calculate_result("Optimized PageRank", durations, total_duration, graph.node_count())
56 }
57
58 pub fn bench_pagerank_simd(&self, graph: &ArrowGraph) -> Result<SimpleBenchmarkResult> {
60 for _ in 0..self.warmup_iterations {
62 let _ = self.run_pagerank_simd(graph);
63 }
64
65 let mut durations = Vec::new();
67 let total_start = Instant::now();
68
69 for _ in 0..self.measurement_iterations {
70 let start = Instant::now();
71 let _ = self.run_pagerank_simd(graph);
72 durations.push(start.elapsed());
73 }
74
75 let total_duration = total_start.elapsed();
76 self.calculate_result("SIMD PageRank", durations, total_duration, graph.node_count())
77 }
78
79 pub fn bench_connected_components(&self, graph: &ArrowGraph) -> Result<SimpleBenchmarkResult> {
81 for _ in 0..self.warmup_iterations {
83 let _ = self.run_connected_components_real(graph);
84 }
85
86 let mut durations = Vec::new();
88 let total_start = Instant::now();
89
90 for _ in 0..self.measurement_iterations {
91 let start = Instant::now();
92 let _ = self.run_connected_components_real(graph);
93 durations.push(start.elapsed());
94 }
95
96 let total_duration = total_start.elapsed();
97 self.calculate_result("Real Connected Components", durations, total_duration, graph.node_count())
98 }
99
100 pub fn bench_graph_construction(&self, num_nodes: usize, num_edges: usize) -> Result<SimpleBenchmarkResult> {
102 for _ in 0..self.warmup_iterations {
104 let _ = self.create_test_graph(num_nodes, num_edges);
105 }
106
107 let mut durations = Vec::new();
109 let total_start = Instant::now();
110
111 for _ in 0..self.measurement_iterations {
112 let start = Instant::now();
113 let _ = self.create_test_graph(num_nodes, num_edges);
114 durations.push(start.elapsed());
115 }
116
117 let total_duration = total_start.elapsed();
118 self.calculate_result("Graph Construction", durations, total_duration, num_nodes)
119 }
120
121 fn calculate_result(
123 &self,
124 name: &str,
125 durations: Vec<Duration>,
126 total_duration: Duration,
127 operations: usize,
128 ) -> Result<SimpleBenchmarkResult> {
129 let min_duration = durations.iter().min().copied().unwrap_or(Duration::from_secs(0));
130 let max_duration = durations.iter().max().copied().unwrap_or(Duration::from_secs(0));
131 let avg_duration = Duration::from_nanos(
132 (durations.iter().map(|d| d.as_nanos()).sum::<u128>() / durations.len() as u128) as u64
133 );
134
135 let throughput_ops_per_sec = if avg_duration.as_secs_f64() > 0.0 {
136 operations as f64 / avg_duration.as_secs_f64()
137 } else {
138 0.0
139 };
140
141 Ok(SimpleBenchmarkResult {
142 name: name.to_string(),
143 avg_duration,
144 min_duration,
145 max_duration,
146 total_duration,
147 iterations: durations.len(),
148 throughput_ops_per_sec,
149 })
150 }
151
152 fn run_pagerank_real(&self, graph: &ArrowGraph) -> Vec<f64> {
154 let damping_factor = 0.85;
156 let tolerance = 1e-6;
157 let max_iterations = 50;
158
159 let node_ids: Vec<String> = graph.node_ids().cloned().collect();
161 let node_count = node_ids.len();
162
163 if node_count == 0 {
164 return Vec::new();
165 }
166
167 let mut node_to_index = HashMap::with_capacity(node_count);
169 for (i, node_id) in node_ids.iter().enumerate() {
170 node_to_index.insert(node_id, i);
171 }
172
173 let mut sparse_neighbors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
175 let mut out_degrees = vec![0; node_count];
176
177 for (i, node_id) in node_ids.iter().enumerate() {
179 if let Some(neighbors) = graph.neighbors(node_id) {
180 out_degrees[i] = neighbors.len();
181 sparse_neighbors[i].reserve(neighbors.len());
182
183 for neighbor in neighbors {
184 if let Some(&neighbor_idx) = node_to_index.get(neighbor) {
185 sparse_neighbors[i].push(neighbor_idx);
186 }
187 }
188 }
189 }
190
191 let initial_rank = 1.0 / node_count as f64;
193 let mut current_ranks = vec![initial_rank; node_count];
194 let mut new_ranks = vec![0.0; node_count];
195
196 let base_rank = (1.0 - damping_factor) / node_count as f64;
198
199 for iteration in 0..max_iterations {
201 for rank in new_ranks.iter_mut() {
203 *rank = base_rank;
204 }
205
206 let mut dangling_contribution = 0.0;
208 for i in 0..node_count {
209 if out_degrees[i] == 0 {
210 dangling_contribution += current_ranks[i];
211 }
212 }
213 dangling_contribution = damping_factor * dangling_contribution / node_count as f64;
214
215 for rank in new_ranks.iter_mut() {
217 *rank += dangling_contribution;
218 }
219
220 for i in 0..node_count {
222 if !sparse_neighbors[i].is_empty() {
223 let contribution = damping_factor * current_ranks[i] / sparse_neighbors[i].len() as f64;
224
225 for &neighbor_idx in &sparse_neighbors[i] {
227 new_ranks[neighbor_idx] += contribution;
228 }
229 }
230 }
231
232 let mut diff = 0.0;
234 for i in 0..node_count {
235 diff += (new_ranks[i] - current_ranks[i]).abs();
236 }
237
238 std::mem::swap(&mut current_ranks, &mut new_ranks);
240
241 if diff < tolerance {
243 println!("PageRank converged after {} iterations", iteration + 1);
244 break;
245 }
246 }
247
248 current_ranks
249 }
250
251 fn run_pagerank_simd(&self, graph: &ArrowGraph) -> Vec<f64> {
252 let damping_factor = 0.85;
254 let tolerance = 1e-6;
255 let max_iterations = 50;
256
257 let node_ids: Vec<String> = graph.node_ids().cloned().collect();
258 let node_count = node_ids.len();
259
260 if node_count == 0 {
261 return Vec::new();
262 }
263
264 let simd_size = ((node_count + 3) / 4) * 4;
266 let mut node_to_index = HashMap::with_capacity(node_count);
267 for (i, node_id) in node_ids.iter().enumerate() {
268 node_to_index.insert(node_id, i);
269 }
270
271 let mut sparse_neighbors: Vec<Vec<usize>> = vec![Vec::new(); node_count];
273 let mut out_degrees = vec![0; node_count];
274
275 for (i, node_id) in node_ids.iter().enumerate() {
276 if let Some(neighbors) = graph.neighbors(node_id) {
277 out_degrees[i] = neighbors.len();
278 sparse_neighbors[i].reserve(neighbors.len());
279
280 for neighbor in neighbors {
281 if let Some(&neighbor_idx) = node_to_index.get(neighbor) {
282 sparse_neighbors[i].push(neighbor_idx);
283 }
284 }
285 }
286 }
287
288 let initial_rank = 1.0 / node_count as f64;
290 let mut current_ranks = vec![initial_rank; simd_size];
291 let mut new_ranks = vec![0.0; simd_size];
292
293 for i in node_count..simd_size {
295 current_ranks[i] = 0.0;
296 }
297
298 let base_rank = (1.0 - damping_factor) / node_count as f64;
299 let base_rank_simd = f64x4::splat(base_rank);
300
301 for iteration in 0..max_iterations {
302 for chunk in new_ranks.chunks_exact_mut(4) {
304 let base_array: [f64; 4] = [base_rank; 4];
305 chunk.copy_from_slice(&base_array);
306 }
307
308 let mut dangling_contribution = 0.0;
310 for i in 0..node_count {
311 if out_degrees[i] == 0 {
312 dangling_contribution += current_ranks[i];
313 }
314 }
315 dangling_contribution = damping_factor * dangling_contribution / node_count as f64;
316 let dangling_simd = f64x4::splat(dangling_contribution);
317
318 for chunk in new_ranks.chunks_exact_mut(4) {
320 if chunk.len() == 4 {
321 let current_chunk = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
322 let updated_chunk = current_chunk + dangling_simd;
323 let result_array: [f64; 4] = updated_chunk.into();
324 chunk.copy_from_slice(&result_array);
325 }
326 }
327
328 for i in 0..node_count {
330 if !sparse_neighbors[i].is_empty() {
331 let contribution = damping_factor * current_ranks[i] / sparse_neighbors[i].len() as f64;
332
333 for &neighbor_idx in &sparse_neighbors[i] {
334 new_ranks[neighbor_idx] += contribution;
335 }
336 }
337 }
338
339 let mut diff_accumulator = f64x4::splat(0.0);
341
342 for i in (0..node_count).step_by(4) {
343 let end = (i + 4).min(simd_size);
344 if end - i == 4 && i + 4 <= node_count {
345 let new_chunk = f64x4::new([new_ranks[i], new_ranks[i+1], new_ranks[i+2], new_ranks[i+3]]);
346 let current_chunk = f64x4::new([current_ranks[i], current_ranks[i+1], current_ranks[i+2], current_ranks[i+3]]);
347 let diff_chunk = (new_chunk - current_chunk).abs();
348 diff_accumulator += diff_chunk;
349 } else {
350 for j in i..end.min(node_count) {
352 let scalar_diff = (new_ranks[j] - current_ranks[j]).abs();
353 diff_accumulator += f64x4::splat(scalar_diff);
354 }
355 }
356 }
357
358 let diff_array: [f64; 4] = diff_accumulator.into();
360 let total_diff = diff_array.iter().sum::<f64>();
361
362 std::mem::swap(&mut current_ranks, &mut new_ranks);
364
365 if total_diff < tolerance {
366 println!("SIMD PageRank converged after {} iterations", iteration + 1);
367 break;
368 }
369 }
370
371 current_ranks.truncate(node_count);
373 current_ranks
374 }
375
376 fn run_connected_components_real(&self, graph: &ArrowGraph) -> Vec<usize> {
377 let node_ids: Vec<String> = graph.node_ids().cloned().collect();
379 let node_count = node_ids.len();
380
381 if node_count == 0 {
382 return Vec::new();
383 }
384
385 let node_to_index: std::collections::HashMap<String, usize> = node_ids
387 .iter()
388 .enumerate()
389 .map(|(i, id)| (id.clone(), i))
390 .collect();
391
392 let mut parent = vec![0; node_count];
394 let mut rank = vec![0; node_count];
395
396 for i in 0..node_count {
398 parent[i] = i;
399 }
400
401 fn find(parent: &mut [usize], x: usize) -> usize {
403 if parent[x] != x {
404 parent[x] = find(parent, parent[x]); }
406 parent[x]
407 }
408
409 fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) {
411 let root_x = find(parent, x);
412 let root_y = find(parent, y);
413
414 if root_x != root_y {
415 if rank[root_x] < rank[root_y] {
416 parent[root_x] = root_y;
417 } else if rank[root_x] > rank[root_y] {
418 parent[root_y] = root_x;
419 } else {
420 parent[root_y] = root_x;
421 rank[root_x] += 1;
422 }
423 }
424 }
425
426 for (i, node_id) in node_ids.iter().enumerate() {
428 if let Some(neighbors) = graph.neighbors(node_id) {
429 for neighbor in neighbors {
430 if let Some(&neighbor_idx) = node_to_index.get(neighbor) {
431 union(&mut parent, &mut rank, i, neighbor_idx);
432 }
433 }
434 }
435 }
436
437 let mut components = Vec::with_capacity(node_count);
439 for i in 0..node_count {
440 components.push(find(&mut parent, i));
441 }
442
443 let mut component_map = std::collections::HashMap::new();
445 let mut next_component_id = 0;
446
447 for component in &mut components {
448 if !component_map.contains_key(component) {
449 component_map.insert(*component, next_component_id);
450 next_component_id += 1;
451 }
452 *component = component_map[component];
453 }
454
455 components
456 }
457
458 fn create_test_graph(&self, num_nodes: usize, num_edges: usize) -> ArrowGraph {
459 use arrow::array::{StringArray, RecordBatch};
460 use arrow::datatypes::{DataType, Field, Schema};
461 use std::sync::Arc;
462
463 let node_schema = Arc::new(Schema::new(vec![
465 Field::new("id", DataType::Utf8, false),
466 ]));
467
468 let edge_schema = Arc::new(Schema::new(vec![
469 Field::new("source", DataType::Utf8, false),
470 Field::new("target", DataType::Utf8, false),
471 ]));
472
473 let node_ids: Vec<String> = (0..num_nodes.min(100)).map(|i| format!("node_{}", i)).collect();
475 let edge_sources: Vec<String> = (0..num_edges.min(100)).map(|i| format!("node_{}", i % node_ids.len())).collect();
476 let edge_targets: Vec<String> = (0..num_edges.min(100)).map(|i| format!("node_{}", (i + 1) % node_ids.len())).collect();
477
478 let nodes_batch = RecordBatch::try_new(
479 node_schema,
480 vec![Arc::new(StringArray::from(node_ids))],
481 ).unwrap();
482
483 let edges_batch = RecordBatch::try_new(
484 edge_schema,
485 vec![
486 Arc::new(StringArray::from(edge_sources)),
487 Arc::new(StringArray::from(edge_targets)),
488 ],
489 ).unwrap();
490
491 ArrowGraph::new(nodes_batch, edges_batch).unwrap()
492 }
493}
494
495pub fn run_all_benchmarks(graph: &ArrowGraph) -> Result<Vec<SimpleBenchmarkResult>> {
497 let benchmark = SimpleBenchmark::new("Arrow Graph Performance");
498 let mut results = Vec::new();
499
500 println!("Running simple benchmark suite...");
501
502 println!("Running Optimized PageRank benchmark...");
504 let pagerank_result = benchmark.bench_pagerank(graph)?;
505 results.push(pagerank_result);
506
507 println!("Running SIMD PageRank benchmark...");
509 let simd_pagerank_result = benchmark.bench_pagerank_simd(graph)?;
510 results.push(simd_pagerank_result);
511
512 println!("Running Connected Components benchmark...");
514 let cc_result = benchmark.bench_connected_components(graph)?;
515 results.push(cc_result);
516
517 println!("Running Graph Construction benchmark...");
519 let construction_result = benchmark.bench_graph_construction(1000, 5000)?;
520 results.push(construction_result);
521
522 println!("Benchmark suite completed!");
523 Ok(results)
524}
525
526pub fn print_results(results: &[SimpleBenchmarkResult]) {
528 println!("\n{:-<80}", "");
529 println!("{:^80}", "ARROW GRAPH BENCHMARK RESULTS");
530 println!("{:-<80}", "");
531 println!("{:<25} {:>12} {:>12} {:>12} {:>15}",
532 "Benchmark", "Avg (ms)", "Min (ms)", "Max (ms)", "Throughput (ops/s)");
533 println!("{:-<80}", "");
534
535 for result in results {
536 println!("{:<25} {:>12.2} {:>12.2} {:>12.2} {:>15.0}",
537 result.name,
538 result.avg_duration.as_millis(),
539 result.min_duration.as_millis(),
540 result.max_duration.as_millis(),
541 result.throughput_ops_per_sec);
542 }
543
544 println!("{:-<80}", "");
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 #[test]
552 fn test_simple_benchmark_creation() {
553 let bench = SimpleBenchmark::new("test");
554 assert_eq!(bench.name, "test");
555 assert_eq!(bench.warmup_iterations, 3);
556 assert_eq!(bench.measurement_iterations, 10);
557 }
558
559 #[test]
560 fn test_pagerank_benchmark() {
561 let bench = SimpleBenchmark::new("test");
562 let mut graph = ArrowGraph::new();
563
564 for i in 0..100 {
566 let _ = graph.add_node(&format!("node_{}", i), None);
567 }
568
569 let result = bench.bench_pagerank(&graph).unwrap();
570 assert_eq!(result.name, "PageRank");
571 assert!(result.avg_duration.as_nanos() > 0);
572 assert_eq!(result.iterations, 10);
573 }
574
575 #[test]
576 fn test_graph_construction_benchmark() {
577 let bench = SimpleBenchmark::new("test");
578 let result = bench.bench_graph_construction(100, 200).unwrap();
579 assert_eq!(result.name, "Graph Construction");
580 assert!(result.avg_duration.as_nanos() > 0);
581 }
582}