Я пытаюсь обернуть Rustfft в функции, которые я могу использовать через привязки из другого языка. Чтобы добиться этого, мне нужны две функции: одна для создания плана, другая для запуска fft. Чтобы упростить обмен данными и генерацию привязки, я хочу, чтобы одним из параметров был список f64. Я обнаружил, что при копировании входных и выходных данных существует, по крайней мере, некоторое снижение производительности, поэтому я считаю, что было бы полезно позволить ржавчине запускать переданные данные на месте.
Вот мой код на данный момент. При завершении основного процесса выдается ошибка STATUS_HEAP_CORRUPTION, но я не смог понять, почему я получаю эту ошибку и что я сделал не так.
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)
}
Результат, который я получаю, показывает действительный результат БПФ:
Компиляция Rust_fft_test v0.1.0 (C:\Users\Markus\dev\rust\rust_fft_test) Выполнены цели профиля
dev
[неоптимизированный + debuginfo] за 1,45 с. Запускtarget\debug\rust_fft_test.exe
необработанного буфера перед 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] Необработанный буфер после 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] ошибка: процесс не завершился успешно :target\debug\rust_fft_test.exe
(код выхода: 0xc0000374, STATUS_HEAP_CORRUPTION)
- Терминальный процесс «C:\Users\Markus.cargo\bin\cargo.exe 'run», '--package', 'rust_fft_test', '--bin', 'rust_fft_test'" прекращено с кодом выхода: -1073740940. * Терминал будет повторно использоваться задачами, нажмите любую клавишу, чтобы закрыть его.
Конечно, вам очень хочется использовать bytemuck.
Vec<f64>
иVec<Complex<f64>>
имеют одинаковое расположение в памяти
Это не верно. Complex
действительно является #[repr(C)]
, поэтому его расположение гарантировано, но оно содержит два f64
, поэтому его размер вдвое больше одного.
Вам необходимо отрегулировать длину:
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)
}
Это автоматически пропустит последний элемент, если длина нечетная.
Или лучше используйте bytemuck, чтобы сделать это безопасно. num-complex
имеет интеграцию с ним, но вам необходимо включить функцию bytemuck
:
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)
}
Это вызовет панику, если длина будет нечетной. Есть и ошибочный вариант.
Вы преобразуете
&mut [f64]
в&mut [Complex<f64>]
, ноComplex<f64>
в два раза большеf64
, поэтому полученный срез достигает памяти за пределами выделения исходного среза: неопределенное поведение.