yum/vfft

git clone https://git.yummers.dev/yum/vfft

yumRemove hot-path allocation from fft drainc1d59be

master
5.2 KiB177 linesraw
1use num::complex::Complex;
2use num::traits::{Float, FloatConst};
3
4// Evaluates the base-`k` logarithm of `n`.
5// Precondition: `n` is a power of `k`.
6pub const fn log_k_of<const K: usize>(mut n: usize) -> usize {
7    let mut res = 0;
8    while n > 1 {
9        n /= K;
10        res += 1;
11    }
12    res
13}
14
15pub fn is_power_of_k(n: usize, k: usize) -> bool {
16    match n {
17        0 => false,
18        1 => true,
19        _ => n % k == 0 && is_power_of_k(n / k, k),
20    }
21}
22
23pub fn usize_to_float<T: Float>(value: usize) -> T {
24    num::cast(value).unwrap()
25}
26
27// Calculates the "twiddle factors" for an n-element FFT, aka all of the nth roots of unity.
28pub fn precompute_twiddles<T: Float + FloatConst>(n: usize) -> Vec<Complex<T>> {
29    let mut result = vec![Complex::<T>::new(T::zero(), T::zero()); n];
30
31    let n_f64 = usize_to_float::<f64>(n);
32    for i in 0..n {
33        let tw_f64 = Complex::<f64>::cis(-f64::TAU() * usize_to_float::<f64>(i) / (n_f64));
34        result[i] = Complex::new(T::from(tw_f64.re).unwrap(), T::from(tw_f64.im).unwrap());
35    }
36
37    result
38}
39
40// A FIFO data structure that stores a fixed number of elements. When it fills
41// up, pushing overwrites the oldest element.
42pub struct RingBuffer<T: Clone + Default> {
43    data: Vec<T>,
44    // Start points to the oldest element. End points to the next available
45    // slot.
46    start: usize,
47    end: usize,
48    // The number of elements currently in the buffer.
49    n_elems: usize,
50}
51
52impl<T: Clone + Default> RingBuffer<T> {
53    pub fn new(capacity: usize) -> Self {
54        assert!(capacity > 0);
55        Self {
56            data: vec![T::default(); capacity],
57            start: 0,
58            end: 0,
59            n_elems: 0,
60        }
61    }
62
63    fn advance(&self, index: usize) -> usize {
64        (index + 1) % self.data.len()
65    }
66
67    // Pop one element. Returns an error if the buffer is empty.
68    pub fn pop(&mut self) -> anyhow::Result<&T> {
69        if self.n_elems == 0 {
70            std::hint::cold_path();
71            return Err(anyhow::anyhow!("RingBuffer is empty"));
72        }
73        let result = &self.data[self.start];
74        self.start = self.advance(self.start);
75        self.n_elems -= 1;
76        Ok(result)
77    }
78
79    // Pop elements until `cb` return false. If `cb` returns an error, we
80    // propagate it immediately and do not remove the element.
81    pub fn pop_each(
82        &mut self,
83        mut cb: impl FnMut(&T) -> anyhow::Result<bool>,
84    ) -> anyhow::Result<()> {
85        while self.n_elems > 0 {
86            let keep_going = cb(&self.data[self.start])?;
87            self.pop()?;
88            if !keep_going {
89                break;
90            }
91        }
92        Ok(())
93    }
94
95    // Push one element.
96    pub fn push(&mut self, value: T) {
97        self.data[self.end] = value;
98        self.end = self.advance(self.end);
99        if self.n_elems == self.data.len() {
100            self.start = self.advance(self.start);
101        } else {
102            self.n_elems += 1;
103        }
104    }
105
106    // Push a slice of elements.
107    pub fn push_slice(&mut self, values: &[T]) {
108        let capacity = self.data.len();
109        // Ensure that `values` is not longer than our ringbuf capacity.
110        let values = &values[values.len().saturating_sub(capacity)..];
111
112        // Calculate number of elements to copy over after the end pointer.
113        let n_after_end = values.len().min(capacity - self.end);
114        let values_after_end = &values[..n_after_end];
115        self.data[self.end..self.end + n_after_end].clone_from_slice(values_after_end);
116
117        let values_before_start = &values[n_after_end..];
118        self.data[..values_before_start.len()].clone_from_slice(values_before_start);
119
120        let n_overwritten = values.len().saturating_sub(capacity - self.n_elems);
121        self.start = (self.start + n_overwritten) % capacity;
122        self.end = (self.end + values.len()) % capacity;
123        self.n_elems += values.len() - n_overwritten;
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn ringbuf_tests() {
133        // Sanity.
134        {
135            let mut rb = RingBuffer::<i32>::new(3);
136            assert!(rb.pop().is_err());
137            rb.push(1);
138            assert_eq!(*rb.pop().unwrap(), 1);
139            rb.push(2);
140            rb.push(3);
141            rb.push(4);
142            assert_eq!(*rb.pop().unwrap(), 2);
143            assert_eq!(*rb.pop().unwrap(), 3);
144            assert_eq!(*rb.pop().unwrap(), 4);
145            assert!(rb.pop().is_err());
146            rb.push_slice(&[5, 6, 7, 8]);
147            assert_eq!(*rb.pop().unwrap(), 6);
148            assert_eq!(*rb.pop().unwrap(), 7);
149            assert_eq!(*rb.pop().unwrap(), 8);
150            assert!(rb.pop().is_err());
151        }
152        // Callback access.
153        {
154            let mut rb = RingBuffer::<i32>::new(3);
155            rb.push(1);
156            rb.push(2);
157            rb.push(3);
158            rb.push(4);
159            let mut iter = 2;
160            assert!(
161                rb.pop_each(|x| {
162                    assert_eq!(*x, iter);
163                    iter += 1;
164                    Ok(true)
165                })
166                .is_ok()
167            );
168        }
169        // Size-1 edge cases.
170        {
171            let mut rb = RingBuffer::<i32>::new(1);
172            rb.push(1);
173            assert_eq!(*rb.pop().unwrap(), 1);
174            assert!(rb.pop().is_err());
175        }
176    }
177}