I am trying to wrap rustfft in functions that I can use via bindings from another language. In order to achieve this, I need two functions: one to create the plan, one to run the fft. To simplify data exchange and binding generation, I want one of the parameters to be a list of f64. I found that there's at least some performance penalty when copying around input and output data, so I believe it might be beneficial to let rustfft run on the passed data in-place.
Here's my code so far. It gives an error with a STATUS_HEAP_CORRUPTION when main finishes, but I could not find out why I get this error and what I did wrong.
use rustfft::{Fft, FftPlanner};
fn main() {
const SIZE: usize = 8;
let fft = create_fft_plan(SIZE);
let mut buffer_raw = vec![0.0f64; SIZE * 2];
buffer_raw[0] = 1.0;
println!("Raw buffer before fft: {:?}", buffer_raw);
run_fft(&fft, &mut buffer_raw);
println!("Raw buffer after fft: {:?}", buffer_raw);
}
pub fn create_fft_plan(size: usize) -> std::sync::Arc<dyn Fft<f64>> {
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(size);
fft
}
pub fn run_fft(fft: &std::sync::Arc<dyn Fft<f64>>, buffer: &mut [f64]) {
// We want to modify buffer in-place for performance reasons: This function is likely
// to be called very often with big buffers. Vec<f64> and Vec<Complex<f64>> have the same
// layout in memory as far as I found out so far.
unsafe {
let buffer_slice = workaround_transmute_mut(buffer);
fft.process(buffer_slice);
};
}
// This function was taken from rustfft-6.2.0\src\array_utils.rs
unsafe fn workaround_transmute_mut<T, U>(slice: &mut [T]) -> &mut [U] {
let ptr = slice.as_mut_ptr() as *mut U;
let len = slice.len();
std::slice::from_raw_parts_mut(ptr, len)
}
The output that I get shows a valid fft result:
Compiling rust_fft_test v0.1.0 (C:\Users\Markus\dev\rust\rust_fft_test) Finished
dev
profile [unoptimized + debuginfo] target(s) in 1.45s Runningtarget\debug\rust_fft_test.exe
Raw buffer before fft: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] Raw buffer after fft: [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] error: process didn't exit successfully:target\debug\rust_fft_test.exe
(exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)
- The terminal process "C:\Users\Markus.cargo\bin\cargo.exe 'run', '--package', 'rust_fft_test', '--bin', 'rust_fft_test'" terminated with exit code: -1073740940. * Terminal will be reused by tasks, press any key to close it.
Vec<f64>
andVec<Complex<f64>>
have the same layout in memory
This is wrong. Complex
is indeed #[repr(C)]
so its layout is guaranteed, but it contains two f64
s, so it is double the size of one.
You need to adjust the length:
unsafe fn workaround_transmute_mut(slice: &mut [f64]) -> &mut [Complex<f64>] {
let ptr = slice.as_mut_ptr() as *mut U;
let len = slice.len() / 2;
std::slice::from_raw_parts_mut(ptr, len)
}
This will silently skip the last element if the length is odd.
Or better, use bytemuck
to do it safely. num-complex
has integration with it, but you need to enable the bytemuck
feature:
num-complex = { version = "0.4.6", features = ["bytemuck"] }
bytemuck = "1.16.1"
fn workaround_transmute_mut(slice: &mut [f64]) -> &mut [Complex<f64>] {
bytemuck::cast_slice_mut(slice)
}
This will panic if the length is odd. There is also a fallible variant.