1use std::time::Instant;
7
8use handwired_rs::handwired;
9use handwired_rs::{BatchExecutor, LANES, TensorExecutor};
10use handwired_rs_gpu::{Gpu, GpuBatch};
11
12const TEXT: usize = 32;
13
14#[handwired]
16fn find_pattern(text: [u8; 32], pattern: [u8; 3]) -> (bool, u8) {
17 for start in 0..30 {
18 if text[start] == pattern[0] && text[start + 1] == pattern[1] && text[start + 2] == pattern[2] {
19 return (true, start as u8);
20 }
21 }
22 (false, 0)
23}
24
25fn main() {
26 let records = 100_000;
27 let words: [&[u8]; 8] = [b"cat ", b"dog ", b"the ", b"cart ", b"scatter ", b"a ", b"category ", b"doge "];
28 let mut seed = 0x2545_F491_4F6C_DD1Du64;
29 let mut next = move || {
30 seed ^= seed << 13;
31 seed ^= seed >> 7;
32 seed ^= seed << 17;
33 seed
34 };
35 let inputs: Vec<([u8; TEXT], [u8; 3])> = (0..records)
36 .map(|_| {
37 let mut text = Vec::new();
38 while text.len() < TEXT {
39 text.extend_from_slice(words[(next() % 8) as usize]);
40 }
41 let text: [u8; TEXT] = text[..TEXT].try_into().unwrap();
42 let pattern = if next() % 2 == 0 { *b"cat" } else { *b"dog" };
43 (text, pattern)
44 })
45 .collect();
46
47 let start = Instant::now();
48 let compiled = find_pattern_circuit();
49 println!(
50 "circuit: {} live operations in {} layers, built in {:.1?}",
51 compiled.program.live_operation_count(),
52 compiled.program.layers.len(),
53 start.elapsed()
54 );
55
56 let start = Instant::now();
57 let native: Vec<(bool, u8)> = inputs.iter().map(|&(text, pattern)| find_pattern(text, pattern)).collect();
58 let native_time = start.elapsed();
59
60 let start = Instant::now();
61 let mut executor = TensorExecutor::new(&compiled.program);
62 let mut source = Vec::with_capacity(TEXT + 3);
63 let cpu: Vec<(bool, u8)> = inputs
64 .iter()
65 .map(|record| {
66 source.clear();
67 handwired_rs::dsl::Params::encode(&(record.0, record.1), &mut source);
68 let outputs = executor.run(&source);
69 (outputs[0] == 1.0, outputs[1] as u8)
70 })
71 .collect();
72 let cpu_time = start.elapsed();
73
74 let mut sources = Vec::with_capacity(records * (TEXT + 3));
75 for record in &inputs {
76 handwired_rs::dsl::Params::encode(record, &mut sources);
77 }
78 let decode = |outputs: Vec<f32>| -> Vec<(bool, u8)> {
79 outputs.chunks(2).map(|record| (record[0] == 1.0, record[1] as u8)).collect()
80 };
81 let start = Instant::now();
82 let batched = decode(BatchExecutor::new(&compiled.program).run(&sources));
83 let batch_time = start.elapsed();
84 let start = Instant::now();
85 let parallel = decode(BatchExecutor::run_parallel(&compiled.program, &sources, None));
86 let parallel_time = start.elapsed();
87 let threads = std::thread::available_parallelism().map_or(1, |count| count.get());
88
89 let gpu = Gpu::new().expect("a GPU adapter");
90 let start = Instant::now();
91 let batch = GpuBatch::new(&gpu, &compiled).unwrap();
92 let shader_time = start.elapsed();
93 let _ = batch.run(&gpu, &inputs[..1024]).unwrap();
94 let start = Instant::now();
95 let on_gpu = batch.run(&gpu, &inputs).unwrap();
96 let gpu_time = start.elapsed();
97
98 assert_eq!(cpu, native, "the CPU tensor executor disagrees with the function");
99 assert_eq!(batched, native, "the batch executor disagrees with the function");
100 assert_eq!(parallel, native, "the parallel batch executor disagrees with the function");
101 assert_eq!(on_gpu, native, "the GPU disagrees with the function");
102 let found = native.iter().filter(|result| result.0).count();
103 println!("{records} records, {found} contain their pattern; every result matches the function");
104 println!("plain Rust: {native_time:>10.1?}");
105 println!("CPU tensor executor: {cpu_time:>10.1?} (one record at a time)");
106 println!("CPU batch executor: {batch_time:>10.1?} ({LANES} records at a time, one thread)");
107 println!("CPU batch executor: {parallel_time:>10.1?} ({threads} threads)");
108 println!(
109 "GPU ({}, {:?}): {gpu_time:>10.1?} (upload, run, read back; shader built once in {shader_time:.1?})",
110 gpu.info.name, gpu.info.device_type
111 );
112}