357 lines
11 KiB
Rust
357 lines
11 KiB
Rust
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
|
use std::time::Duration;
|
|
|
|
/// Benchmark individual preprocessing transforms
|
|
fn bench_individual_transforms(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("individual_transforms");
|
|
group.measurement_time(Duration::from_secs(8));
|
|
|
|
let sizes = [(224, 224), (384, 384), (512, 512)];
|
|
|
|
for (w, h) in sizes {
|
|
let image_data = generate_test_image(w, h);
|
|
|
|
// Grayscale conversion
|
|
group.bench_with_input(
|
|
BenchmarkId::new("grayscale", format!("{}x{}", w, h)),
|
|
&image_data,
|
|
|b, img| {
|
|
b.iter(|| black_box(convert_to_grayscale(black_box(img), w, h)));
|
|
},
|
|
);
|
|
|
|
// Gaussian blur
|
|
group.bench_with_input(
|
|
BenchmarkId::new("gaussian_blur", format!("{}x{}", w, h)),
|
|
&image_data,
|
|
|b, img| {
|
|
b.iter(|| black_box(apply_gaussian_blur(black_box(img), w, h, 5)));
|
|
},
|
|
);
|
|
|
|
// Adaptive threshold
|
|
group.bench_with_input(
|
|
BenchmarkId::new("threshold", format!("{}x{}", w, h)),
|
|
&image_data,
|
|
|b, img| {
|
|
b.iter(|| black_box(apply_adaptive_threshold(black_box(img), w, h)));
|
|
},
|
|
);
|
|
|
|
// Edge detection
|
|
group.bench_with_input(
|
|
BenchmarkId::new("edge_detection", format!("{}x{}", w, h)),
|
|
&image_data,
|
|
|b, img| {
|
|
b.iter(|| black_box(detect_edges(black_box(img), w, h)));
|
|
},
|
|
);
|
|
|
|
// Normalization
|
|
group.bench_with_input(
|
|
BenchmarkId::new("normalize", format!("{}x{}", w, h)),
|
|
&image_data,
|
|
|b, img| {
|
|
b.iter(|| black_box(normalize_image(black_box(img))));
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark full preprocessing pipeline
|
|
fn bench_full_pipeline(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("full_pipeline");
|
|
group.measurement_time(Duration::from_secs(10));
|
|
|
|
let sizes = [(224, 224), (384, 384), (512, 512)];
|
|
|
|
for (w, h) in sizes {
|
|
let image_data = generate_test_image(w, h);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("sequential", format!("{}x{}", w, h)),
|
|
&(image_data.clone(), w, h),
|
|
|b, (img, width, height)| {
|
|
b.iter(|| {
|
|
let gray = convert_to_grayscale(black_box(img), *width, *height);
|
|
let blurred = apply_gaussian_blur(&gray, *width, *height, 5);
|
|
let threshold = apply_adaptive_threshold(&blurred, *width, *height);
|
|
let edges = detect_edges(&threshold, *width, *height);
|
|
let normalized = normalize_image(&edges);
|
|
black_box(normalized)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark parallel vs sequential preprocessing
|
|
fn bench_parallel_vs_sequential(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("parallel_vs_sequential");
|
|
group.measurement_time(Duration::from_secs(10));
|
|
|
|
// Create batch of images
|
|
let batch_size = 8;
|
|
let size = (384, 384);
|
|
let images: Vec<Vec<u8>> = (0..batch_size)
|
|
.map(|_| generate_test_image(size.0, size.1))
|
|
.collect();
|
|
|
|
// Sequential processing
|
|
group.bench_function("sequential_batch", |b| {
|
|
b.iter(|| {
|
|
let results: Vec<_> = images
|
|
.iter()
|
|
.map(|img| {
|
|
let gray = convert_to_grayscale(black_box(img), size.0, size.1);
|
|
let blurred = apply_gaussian_blur(&gray, size.0, size.1, 5);
|
|
apply_adaptive_threshold(&blurred, size.0, size.1)
|
|
})
|
|
.collect();
|
|
black_box(results)
|
|
});
|
|
});
|
|
|
|
// Parallel processing (simulated with rayon-like chunking)
|
|
group.bench_function("parallel_batch", |b| {
|
|
b.iter(|| {
|
|
// In production, this would use rayon::par_iter()
|
|
let results: Vec<_> = images
|
|
.chunks(2)
|
|
.flat_map(|chunk| {
|
|
chunk.iter().map(|img| {
|
|
let gray = convert_to_grayscale(black_box(img), size.0, size.1);
|
|
let blurred = apply_gaussian_blur(&gray, size.0, size.1, 5);
|
|
apply_adaptive_threshold(&blurred, size.0, size.1)
|
|
})
|
|
})
|
|
.collect();
|
|
black_box(results)
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark resize operations
|
|
fn bench_resize_operations(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("resize_operations");
|
|
group.measurement_time(Duration::from_secs(8));
|
|
|
|
let source_image = generate_test_image(1024, 1024);
|
|
let target_sizes = [(224, 224), (384, 384), (512, 512)];
|
|
|
|
for (target_w, target_h) in target_sizes {
|
|
group.bench_with_input(
|
|
BenchmarkId::new("nearest_neighbor", format!("{}x{}", target_w, target_h)),
|
|
&(target_w, target_h),
|
|
|b, &(tw, th)| {
|
|
b.iter(|| black_box(resize_nearest(&source_image, 1024, 1024, tw, th)));
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("bilinear", format!("{}x{}", target_w, target_h)),
|
|
&(target_w, target_h),
|
|
|b, &(tw, th)| {
|
|
b.iter(|| black_box(resize_bilinear(&source_image, 1024, 1024, tw, th)));
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark target: preprocessing should complete in <20ms
|
|
fn bench_latency_target(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("latency_target_20ms");
|
|
group.measurement_time(Duration::from_secs(10));
|
|
group.sample_size(100);
|
|
|
|
let image_data = generate_test_image(384, 384);
|
|
|
|
group.bench_function("full_pipeline_384x384", |b| {
|
|
b.iter(|| {
|
|
let gray = convert_to_grayscale(black_box(&image_data), 384, 384);
|
|
let blurred = apply_gaussian_blur(&gray, 384, 384, 5);
|
|
let threshold = apply_adaptive_threshold(&blurred, 384, 384);
|
|
let normalized = normalize_image(&threshold);
|
|
black_box(normalized)
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// Mock implementations
|
|
|
|
fn generate_test_image(width: u32, height: u32) -> Vec<u8> {
|
|
let size = (width * height * 3) as usize;
|
|
(0..size).map(|i| ((i * 123 + 456) % 256) as u8).collect()
|
|
}
|
|
|
|
fn convert_to_grayscale(rgb_data: &[u8], width: u32, height: u32) -> Vec<u8> {
|
|
let mut gray = Vec::with_capacity((width * height) as usize);
|
|
for chunk in rgb_data.chunks(3) {
|
|
let r = chunk[0] as u32;
|
|
let g = chunk[1] as u32;
|
|
let b = chunk[2] as u32;
|
|
let gray_value = ((r * 299 + g * 587 + b * 114) / 1000) as u8;
|
|
gray.push(gray_value);
|
|
}
|
|
gray
|
|
}
|
|
|
|
fn apply_gaussian_blur(data: &[u8], width: u32, height: u32, kernel_size: usize) -> Vec<u8> {
|
|
let mut result = Vec::with_capacity(data.len());
|
|
let radius = kernel_size / 2;
|
|
|
|
for y in 0..height {
|
|
for x in 0..width {
|
|
let mut sum = 0u32;
|
|
let mut count = 0u32;
|
|
|
|
for ky in 0..kernel_size {
|
|
for kx in 0..kernel_size {
|
|
let nx = x as i32 + kx as i32 - radius as i32;
|
|
let ny = y as i32 + ky as i32 - radius as i32;
|
|
|
|
if nx >= 0 && nx < width as i32 && ny >= 0 && ny < height as i32 {
|
|
let idx = (ny as u32 * width + nx as u32) as usize;
|
|
sum += data[idx] as u32;
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
result.push((sum / count) as u8);
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn apply_adaptive_threshold(data: &[u8], width: u32, height: u32) -> Vec<u8> {
|
|
let mut result = Vec::with_capacity(data.len());
|
|
let block_size = 11;
|
|
let c = 2;
|
|
|
|
for y in 0..height {
|
|
for x in 0..width {
|
|
let idx = (y * width + x) as usize;
|
|
let pixel = data[idx];
|
|
|
|
// Calculate local mean
|
|
let mut sum = 0u32;
|
|
let mut count = 0u32;
|
|
let radius = block_size / 2;
|
|
|
|
for by in y.saturating_sub(radius)..=(y + radius).min(height - 1) {
|
|
for bx in x.saturating_sub(radius)..=(x + radius).min(width - 1) {
|
|
let bidx = (by * width + bx) as usize;
|
|
sum += data[bidx] as u32;
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
let threshold = (sum / count) as i32 - c;
|
|
result.push(if pixel as i32 > threshold { 255 } else { 0 });
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn detect_edges(data: &[u8], width: u32, height: u32) -> Vec<u8> {
|
|
let mut result = Vec::with_capacity(data.len());
|
|
|
|
// Simple Sobel edge detection
|
|
for y in 0..height {
|
|
for x in 0..width {
|
|
if x == 0 || x == width - 1 || y == 0 || y == height - 1 {
|
|
result.push(0);
|
|
continue;
|
|
}
|
|
|
|
let idx = (y * width + x) as usize;
|
|
let gx = (data[idx + 1] as i32 - data[idx - 1] as i32).abs();
|
|
let gy = (data[idx + width as usize] as i32 - data[idx - width as usize] as i32).abs();
|
|
let magnitude = ((gx * gx + gy * gy) as f32).sqrt().min(255.0);
|
|
|
|
result.push(magnitude as u8);
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn normalize_image(data: &[u8]) -> Vec<f32> {
|
|
data.iter().map(|&x| (x as f32 - 128.0) / 128.0).collect()
|
|
}
|
|
|
|
fn resize_nearest(src: &[u8], src_w: u32, src_h: u32, dst_w: u32, dst_h: u32) -> Vec<u8> {
|
|
let mut result = Vec::with_capacity((dst_w * dst_h) as usize);
|
|
let x_ratio = src_w as f32 / dst_w as f32;
|
|
let y_ratio = src_h as f32 / dst_h as f32;
|
|
|
|
for y in 0..dst_h {
|
|
for x in 0..dst_w {
|
|
let src_x = (x as f32 * x_ratio) as u32;
|
|
let src_y = (y as f32 * y_ratio) as u32;
|
|
let idx = (src_y * src_w + src_x) as usize;
|
|
result.push(src[idx]);
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn resize_bilinear(src: &[u8], src_w: u32, src_h: u32, dst_w: u32, dst_h: u32) -> Vec<u8> {
|
|
let mut result = Vec::with_capacity((dst_w * dst_h) as usize);
|
|
let x_ratio = (src_w - 1) as f32 / dst_w as f32;
|
|
let y_ratio = (src_h - 1) as f32 / dst_h as f32;
|
|
|
|
for y in 0..dst_h {
|
|
for x in 0..dst_w {
|
|
let src_x = x as f32 * x_ratio;
|
|
let src_y = y as f32 * y_ratio;
|
|
|
|
let x1 = src_x.floor() as u32;
|
|
let y1 = src_y.floor() as u32;
|
|
let x2 = (x1 + 1).min(src_w - 1);
|
|
let y2 = (y1 + 1).min(src_h - 1);
|
|
|
|
let q11 = src[(y1 * src_w + x1) as usize] as f32;
|
|
let q21 = src[(y1 * src_w + x2) as usize] as f32;
|
|
let q12 = src[(y2 * src_w + x1) as usize] as f32;
|
|
let q22 = src[(y2 * src_w + x2) as usize] as f32;
|
|
|
|
let wx = src_x - x1 as f32;
|
|
let wy = src_y - y1 as f32;
|
|
|
|
let value = q11 * (1.0 - wx) * (1.0 - wy)
|
|
+ q21 * wx * (1.0 - wy)
|
|
+ q12 * (1.0 - wx) * wy
|
|
+ q22 * wx * wy;
|
|
|
|
result.push(value as u8);
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
bench_individual_transforms,
|
|
bench_full_pipeline,
|
|
bench_parallel_vs_sequential,
|
|
bench_resize_operations,
|
|
bench_latency_target
|
|
);
|
|
criterion_main!(benches);
|