use std::f32::consts::PI;
use std::time::Duration;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
type Runtime = cubecl::wgpu::WgpuRuntime;
const SIZES: &[usize] = &[256, 1_024, 4_096, 16_384, 65_536];
const RADIX4_OUTER_SIZES: &[usize] = &[2_048, 4_096, 8_192, 16_384, 65_536];
const BATCH_SIZES: &[usize] = &[1, 4, 16, 64];
const BATCH_N: usize = 4_096;
const BATCH_FIXED: usize = 16;
fn sine_wave(n: usize) -> Vec<f32> {
(0..n)
.map(|i| (2.0 * PI * i as f32 / n as f32).sin())
.collect()
}
fn make_batch(batch_size: usize, n: usize) -> Vec<Vec<f32>> {
(0..batch_size).map(|_| sine_wave(n)).collect()
}
fn make_spectra(
device: &cubecl::wgpu::WgpuDevice,
batch_size: usize,
n: usize,
) -> Vec<(Vec<f32>, Vec<f32>)> {
make_batch(batch_size, n)
.iter()
.map(|s| gpu_fft::fft::fft::<Runtime>(device, s))
.collect()
}
fn bench_fft(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let input = sine_wave(n);
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &input, |b, input| {
b.iter_batched(
|| input.clone(),
|inp| gpu_fft::fft::fft::<Runtime>(&device, &inp),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_ifft(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let (real, imag) = gpu_fft::fft::fft::<Runtime>(&device, &sine_wave(n));
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(
BenchmarkId::from_parameter(n),
&(real, imag),
|b, (real, imag)| {
b.iter_batched(
|| (real.clone(), imag.clone()),
|(re, im)| gpu_fft::ifft::ifft::<Runtime>(&device, &re, &im),
BatchSize::SmallInput,
);
},
);
}
group.finish();
}
fn bench_roundtrip(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("roundtrip");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let input = sine_wave(n);
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &input, |b, input| {
b.iter_batched(
|| input.clone(),
|inp| {
let (re, im) = gpu_fft::fft::fft::<Runtime>(&device, &inp);
gpu_fft::ifft::ifft::<Runtime>(&device, &re, &im)
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_fft_batch_size(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft_batch/batch_size");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &bs in BATCH_SIZES {
let batch = make_batch(bs, BATCH_N);
group.throughput(Throughput::Elements((bs * BATCH_N) as u64));
group.bench_with_input(BenchmarkId::from_parameter(bs), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| gpu_fft::fft::fft_batch::<Runtime>(&device, &b),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_fft_batch_signal_len(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft_batch/signal_len");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let batch = make_batch(BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| gpu_fft::fft::fft_batch::<Runtime>(&device, &b),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_fft_batch_vs_sequential(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft_batch_vs_sequential");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &bs in BATCH_SIZES {
let batch = make_batch(bs, BATCH_N);
group.throughput(Throughput::Elements((bs * BATCH_N) as u64));
group.bench_with_input(
BenchmarkId::new("batch", bs),
&batch,
|b, batch| {
b.iter_batched(
|| batch.clone(),
|b| gpu_fft::fft::fft_batch::<Runtime>(&device, &b),
BatchSize::SmallInput,
);
},
);
group.bench_with_input(
BenchmarkId::new("sequential", bs),
&batch,
|b, batch| {
b.iter_batched(
|| batch.clone(),
|b| {
b.iter()
.map(|s| gpu_fft::fft::fft::<Runtime>(&device, s))
.collect::<Vec<_>>()
},
BatchSize::SmallInput,
);
},
);
}
group.finish();
}
fn bench_ifft_batch_size(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft_batch/batch_size");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &bs in BATCH_SIZES {
let spectra = make_spectra(&device, bs, BATCH_N);
group.throughput(Throughput::Elements((bs * BATCH_N) as u64));
group.bench_with_input(BenchmarkId::from_parameter(bs), &spectra, |b, spectra| {
b.iter_batched(
|| spectra.clone(),
|sp| gpu_fft::ifft::ifft_batch::<Runtime>(&device, &sp),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_ifft_batch_signal_len(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft_batch/signal_len");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let spectra = make_spectra(&device, BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &spectra, |b, spectra| {
b.iter_batched(
|| spectra.clone(),
|sp| gpu_fft::ifft::ifft_batch::<Runtime>(&device, &sp),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_ifft_batch_vs_sequential(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft_batch_vs_sequential");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &bs in BATCH_SIZES {
let spectra = make_spectra(&device, bs, BATCH_N);
group.throughput(Throughput::Elements((bs * BATCH_N) as u64));
group.bench_with_input(
BenchmarkId::new("batch", bs),
&spectra,
|b, spectra| {
b.iter_batched(
|| spectra.clone(),
|sp| gpu_fft::ifft::ifft_batch::<Runtime>(&device, &sp),
BatchSize::SmallInput,
);
},
);
group.bench_with_input(
BenchmarkId::new("sequential", bs),
&spectra,
|b, spectra| {
b.iter_batched(
|| spectra.clone(),
|sp| {
sp.iter()
.map(|(re, im)| gpu_fft::ifft::ifft::<Runtime>(&device, re, im))
.collect::<Vec<_>>()
},
BatchSize::SmallInput,
);
},
);
}
group.finish();
}
fn bench_roundtrip_batch(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("roundtrip_batch");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &bs in BATCH_SIZES {
let batch = make_batch(bs, BATCH_N);
group.throughput(Throughput::Elements((bs * BATCH_N) as u64));
group.bench_with_input(BenchmarkId::from_parameter(bs), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| {
let spectra = gpu_fft::fft::fft_batch::<Runtime>(&device, &b);
gpu_fft::ifft::ifft_batch::<Runtime>(&device, &spectra)
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_roundtrip_batch_signal_len(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("roundtrip_batch/signal_len");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in SIZES {
let batch = make_batch(BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| {
let spectra = gpu_fft::fft::fft_batch::<Runtime>(&device, &b);
gpu_fft::ifft::ifft_batch::<Runtime>(&device, &spectra)
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_fft_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let input = sine_wave(n);
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &input, |b, input| {
b.iter_batched(
|| input.clone(),
|inp| gpu_fft::fft::fft::<Runtime>(&device, &inp),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_ifft_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let (real, imag) = gpu_fft::fft::fft::<Runtime>(&device, &sine_wave(n));
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(
BenchmarkId::from_parameter(n),
&(real, imag),
|b, (real, imag)| {
b.iter_batched(
|| (real.clone(), imag.clone()),
|(re, im)| gpu_fft::ifft::ifft::<Runtime>(&device, &re, &im),
BatchSize::SmallInput,
);
},
);
}
group.finish();
}
fn bench_roundtrip_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("roundtrip_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let input = sine_wave(n);
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &input, |b, input| {
b.iter_batched(
|| input.clone(),
|inp| {
let (re, im) = gpu_fft::fft::fft::<Runtime>(&device, &inp);
gpu_fft::ifft::ifft::<Runtime>(&device, &re, &im)
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_fft_batch_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("fft_batch_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let batch = make_batch(BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| gpu_fft::fft::fft_batch::<Runtime>(&device, &b),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_ifft_batch_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("ifft_batch_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let spectra = make_spectra(&device, BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &spectra, |b, spectra| {
b.iter_batched(
|| spectra.clone(),
|sp| gpu_fft::ifft::ifft_batch::<Runtime>(&device, &sp),
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_roundtrip_batch_radix4_outer(c: &mut Criterion) {
let device = cubecl::wgpu::WgpuDevice::default();
let mut group = c.benchmark_group("roundtrip_batch_radix4_outer");
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(5));
for &n in RADIX4_OUTER_SIZES {
let batch = make_batch(BATCH_FIXED, n);
group.throughput(Throughput::Elements((BATCH_FIXED * n) as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &batch, |b, batch| {
b.iter_batched(
|| batch.clone(),
|b| {
let spectra = gpu_fft::fft::fft_batch::<Runtime>(&device, &b);
gpu_fft::ifft::ifft_batch::<Runtime>(&device, &spectra)
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
criterion_group!(
benches,
bench_fft,
bench_ifft,
bench_roundtrip,
bench_fft_batch_size,
bench_fft_batch_signal_len,
bench_fft_batch_vs_sequential,
bench_ifft_batch_size,
bench_ifft_batch_signal_len,
bench_ifft_batch_vs_sequential,
bench_roundtrip_batch,
bench_roundtrip_batch_signal_len,
bench_fft_radix4_outer,
bench_ifft_radix4_outer,
bench_roundtrip_radix4_outer,
bench_fft_batch_radix4_outer,
bench_ifft_batch_radix4_outer,
bench_roundtrip_batch_radix4_outer,
);
criterion_main!(benches);