Skip to content

Commit d206eb5

Browse files
chores and performance boost (#9)
* chores: * update crates * fix typos * make clippy happy * update info link perf: * Static Dispatch: By making Rolling<'a, U, F> generic over U: RollableUnivariate<F>, method calls (update, revert, get) use static dispatch (monomorphized code) instead of dynamic (vtable) * same Static Dispatch techinic for iter. * chores: fix typos
1 parent 12715d4 commit d206eb5

5 files changed

Lines changed: 65 additions & 56 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ keywords = ["statistics", "stream"]
1010
exclude = [".github", ".pre-commit-config.yaml"]
1111
readme = "README.md"
1212
[dependencies]
13-
num = "0.4.0"
14-
ordered-float = { version = "3.0", features = ["serde"] }
13+
num = "0.4"
14+
ordered-float = { version = "3.9", features = ["serde"] }
1515

1616
serde = { version = "1.0", features = ["derive"] }
1717
serde_json = "1.0"

src/iter.rs

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ use crate::sum::Sum;
1717
use crate::variance::Variance;
1818

1919
#[doc(hidden)]
20-
pub struct IterStat<I>
20+
pub struct IterStat<U, I>
2121
where
22+
U: Univariate<I::Item>,
2223
I: Iterator,
2324
I::Item: Float + FromPrimitive + AddAssign + SubAssign + 'static,
2425
{
25-
stat: Box<dyn Univariate<I::Item>>,
26+
stat: U,
2627
underlying: I,
2728
}
2829

29-
impl<I> Iterator for IterStat<I>
30+
impl<U, I> Iterator for IterStat<U, I>
3031
where
32+
U: Univariate<I::Item>,
3133
I: Iterator,
3234
I::Item: Float + FromPrimitive + AddAssign + SubAssign,
3335
{
@@ -55,13 +57,13 @@ pub trait IterStatisticsExtend: Iterator {
5557
/// }
5658
///
5759
/// ```
58-
fn online_sum(self) -> IterStat<Self>
60+
fn online_sum(self) -> IterStat<Sum<Self::Item>, Self>
5961
where
6062
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
6163
Self: Sized,
6264
{
6365
IterStat {
64-
stat: Box::new(Sum::new()),
66+
stat: Sum::new(),
6567
underlying: self,
6668
}
6769
}
@@ -77,13 +79,13 @@ pub trait IterStatisticsExtend: Iterator {
7779
/// }
7880
///
7981
/// ```
80-
fn online_mean(self) -> IterStat<Self>
82+
fn online_mean(self) -> IterStat<Mean<Self::Item>, Self>
8183
where
8284
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
8385
Self: Sized,
8486
{
8587
IterStat {
86-
stat: Box::new(Mean::new()),
88+
stat: Mean::new(),
8789
underlying: self,
8890
}
8991
}
@@ -99,13 +101,13 @@ pub trait IterStatisticsExtend: Iterator {
99101
/// }
100102
///
101103
/// ```
102-
fn online_count(self) -> IterStat<Self>
104+
fn online_count(self) -> IterStat<Count<Self::Item>, Self>
103105
where
104106
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
105107
Self: Sized,
106108
{
107109
IterStat {
108-
stat: Box::new(Count::new()),
110+
stat: Count::new(),
109111
underlying: self,
110112
}
111113
}
@@ -124,13 +126,13 @@ pub trait IterStatisticsExtend: Iterator {
124126
/// }
125127
///
126128
/// ```
127-
fn online_ewmean(self, alpha: Self::Item) -> IterStat<Self>
129+
fn online_ewmean(self, alpha: Self::Item) -> IterStat<EWMean<Self::Item>, Self>
128130
where
129131
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
130132
Self: Sized,
131133
{
132134
IterStat {
133-
stat: Box::new(EWMean::new(alpha)),
135+
stat: EWMean::new(alpha),
134136
underlying: self,
135137
}
136138
}
@@ -148,13 +150,13 @@ pub trait IterStatisticsExtend: Iterator {
148150
/// }
149151
///
150152
/// ```
151-
fn online_ewvar(self, alpha: Self::Item) -> IterStat<Self>
153+
fn online_ewvar(self, alpha: Self::Item) -> IterStat<EWVariance<Self::Item>, Self>
152154
where
153155
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
154156
Self: Sized,
155157
{
156158
IterStat {
157-
stat: Box::new(EWVariance::new(alpha)),
159+
stat: EWVariance::new(alpha),
158160
underlying: self,
159161
}
160162
}
@@ -173,13 +175,13 @@ pub trait IterStatisticsExtend: Iterator {
173175
/// }
174176
///
175177
/// ```
176-
fn online_iqr(self, q_inf: Self::Item, q_sup: Self::Item) -> IterStat<Self>
178+
fn online_iqr(self, q_inf: Self::Item, q_sup: Self::Item) -> IterStat<IQR<Self::Item>, Self>
177179
where
178180
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
179181
Self: Sized,
180182
{
181183
IterStat {
182-
stat: Box::new(IQR::new(q_inf, q_sup).expect("q_inf must be strictly less than q_sup")),
184+
stat: IQR::new(q_inf, q_sup).expect("q_inf must be strictly less than q_sup"),
183185
underlying: self,
184186
}
185187
}
@@ -197,13 +199,13 @@ pub trait IterStatisticsExtend: Iterator {
197199
/// }
198200
///
199201
/// ```
200-
fn online_kurtosis(self, bias: bool) -> IterStat<Self>
202+
fn online_kurtosis(self, bias: bool) -> IterStat<Kurtosis<Self::Item>, Self>
201203
where
202204
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
203205
Self: Sized,
204206
{
205207
IterStat {
206-
stat: Box::new(Kurtosis::new(bias)),
208+
stat: Kurtosis::new(bias),
207209
underlying: self,
208210
}
209211
}
@@ -219,13 +221,13 @@ pub trait IterStatisticsExtend: Iterator {
219221
/// }
220222
///
221223
/// ```
222-
fn online_max(self) -> IterStat<Self>
224+
fn online_max(self) -> IterStat<Max<Self::Item>, Self>
223225
where
224226
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
225227
Self: Sized,
226228
{
227229
IterStat {
228-
stat: Box::new(Max::new()),
230+
stat: Max::new(),
229231
underlying: self,
230232
}
231233
}
@@ -241,13 +243,13 @@ pub trait IterStatisticsExtend: Iterator {
241243
/// }
242244
///
243245
/// ```
244-
fn online_abs_max(self) -> IterStat<Self>
246+
fn online_abs_max(self) -> IterStat<AbsMax<Self::Item>, Self>
245247
where
246248
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
247249
Self: Sized,
248250
{
249251
IterStat {
250-
stat: Box::new(AbsMax::new()),
252+
stat: AbsMax::new(),
251253
underlying: self,
252254
}
253255
}
@@ -263,13 +265,13 @@ pub trait IterStatisticsExtend: Iterator {
263265
/// }
264266
///
265267
/// ```
266-
fn online_min(self) -> IterStat<Self>
268+
fn online_min(self) -> IterStat<Min<Self::Item>, Self>
267269
where
268270
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
269271
Self: Sized,
270272
{
271273
IterStat {
272-
stat: Box::new(Min::new()),
274+
stat: Min::new(),
273275
underlying: self,
274276
}
275277
}
@@ -285,13 +287,13 @@ pub trait IterStatisticsExtend: Iterator {
285287
/// }
286288
///
287289
/// ```
288-
fn online_ptp(self) -> IterStat<Self>
290+
fn online_ptp(self) -> IterStat<PeakToPeak<Self::Item>, Self>
289291
where
290292
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
291293
Self: Sized,
292294
{
293295
IterStat {
294-
stat: Box::new(PeakToPeak::new()),
296+
stat: PeakToPeak::new(),
295297
underlying: self,
296298
}
297299
}
@@ -312,13 +314,13 @@ pub trait IterStatisticsExtend: Iterator {
312314
/// assert_eq!(d, t);
313315
/// }
314316
/// ```
315-
fn online_quantile(self, q: Self::Item) -> IterStat<Self>
317+
fn online_quantile(self, q: Self::Item) -> IterStat<Quantile<Self::Item>, Self>
316318
where
317319
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
318320
Self: Sized,
319321
{
320322
IterStat {
321-
stat: Box::new(Quantile::new(q).expect("q should be betweek 0 and 1")),
323+
stat: Quantile::new(q).expect("q should be between 0 and 1"),
322324
underlying: self,
323325
}
324326
}
@@ -336,13 +338,13 @@ pub trait IterStatisticsExtend: Iterator {
336338
/// }
337339
///
338340
/// ```
339-
fn online_skew(self, bias: bool) -> IterStat<Self>
341+
fn online_skew(self, bias: bool) -> IterStat<Skew<Self::Item>, Self>
340342
where
341343
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
342344
Self: Sized,
343345
{
344346
IterStat {
345-
stat: Box::new(Skew::new(bias)),
347+
stat: Skew::new(bias),
346348
underlying: self,
347349
}
348350
}
@@ -360,15 +362,15 @@ pub trait IterStatisticsExtend: Iterator {
360362
/// }
361363
///
362364
/// ```
363-
fn online_var(self, ddof: u32) -> IterStat<Self>
365+
fn online_var(self, ddof: u32) -> IterStat<Variance<Self::Item>, Self>
364366
where
365367
Self::Item: Float + FromPrimitive + AddAssign + SubAssign,
366368
Self: Sized,
367369
{
368370
IterStat {
369-
stat: Box::new(Variance::new(ddof)),
371+
stat: Variance::new(ddof),
370372
underlying: self,
371373
}
372374
}
373375
}
374-
impl<I: Iterator> IterStatisticsExtend for I {}
376+
impl<I: Iterator> IterStatisticsExtend for I {}

src/mean.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,7 @@ impl<F: Float + FromPrimitive + AddAssign + SubAssign> Univariate<F> for Mean<F>
5959

6060
impl<F: Float + FromPrimitive + AddAssign + SubAssign> Revertable<F> for Mean<F> {
6161
fn revert(&mut self, x: F) -> Result<(), &'static str> {
62-
match self.n.revert(x) {
63-
Ok(it) => it,
64-
Err(err) => return Err(err),
65-
};
62+
self.n.revert(x)?;
6663

6764
let count = self.n.get();
6865
if count == F::from_f64(0.).unwrap() {

src/quantile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use serde::{Deserialize, Serialize};
2121
/// assert_eq!(running_quantile.get(), 5.0);
2222
/// ```
2323
/// # References
24-
/// [^1]: [The P² Algorithm for Dynamic Univariateal Computing Calculation of Quantiles and Editor Histograms Without Storing Observations](https://www.cse.wustl.edu/~jain/papers/ftp/psqr.pdf)
24+
/// [^1]: [The P² Algorithm for Dynamic Calculation of Quantiles and Histograms Without Storing Observations](https://www.cse.wustl.edu/~jain/papers/ftp/psqr.pdf)
2525
///
26-
/// [^2]: [P² quantile estimator: estimating the median without storing values](https://aakinshin.net/posts/p2-quantile-estimator/)
26+
/// [^2]: [P² quantile estimator: estimating the median without storing values](https://aakinshin.net/posts/p2-quantile-estimator-intro/)
2727
#[derive(Clone, Debug, Serialize, Deserialize)]
2828
pub struct Quantile<F: Float + FromPrimitive + AddAssign + SubAssign> {
2929
q: F,

src/rolling.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,52 @@ use std::{
2222
/// let data = vec![9.,7.,3.,2.,6.,1., 8., 5., 4.];
2323
/// let mut running_sum: Sum<f64> = Sum::new();
2424
/// // We wrap `running_sum` inside the `Rolling` struct.
25-
/// let mut rolling_sum: Rolling<f64> = Rolling::new(& mut running_sum, 2).unwrap();
25+
/// let mut rolling_sum: Rolling<_, f64> = Rolling::new(&mut running_sum, 2).unwrap();
2626
/// for x in data.iter(){
2727
/// rolling_sum.update(*x as f64);
2828
/// }
2929
/// assert_eq!(rolling_sum.get(), 9.0);
3030
/// ```
31-
32-
pub struct Rolling<'a, F: Float + FromPrimitive + AddAssign + SubAssign> {
33-
to_roll: &'a mut dyn RollableUnivariate<F>,
31+
pub struct Rolling<'a, U, F>
32+
where
33+
U: RollableUnivariate<F>, // Optimization: Generic over U (the concrete type) instead of dyn for static dispatch
34+
F: Float + FromPrimitive + AddAssign + SubAssign,
35+
{
36+
to_roll: &'a mut U, // Optimization: &mut U instead of &mut dyn
3437
window_size: usize,
3538
window: VecDeque<F>,
3639
}
3740

38-
impl<'a, F: Float + FromPrimitive + AddAssign + SubAssign> Rolling<'a, F> {
39-
pub fn new(
40-
to_roll: &'a mut dyn RollableUnivariate<F>,
41-
window_size: usize,
42-
) -> Result<Self, &str> {
41+
impl<'a, U, F> Rolling<'a, U, F>
42+
where
43+
U: RollableUnivariate<F>,
44+
F: Float + FromPrimitive + AddAssign + SubAssign,
45+
{
46+
pub fn new(to_roll: &'a mut U, window_size: usize) -> Result<Self, &'static str> { // Optimization: &'static str for error (clearer, no lifetime tie)
4347
if window_size == 0 {
44-
return Err("Window size should not equals to 0");
48+
return Err("Window size should not equal to 0");
4549
}
4650
Ok(Self {
4751
to_roll,
4852
window_size,
49-
window: VecDeque::new(),
53+
window: VecDeque::with_capacity(window_size), // Optimization: Preallocate to avoid reallocs during growth
5054
})
5155
}
5256
}
5357

54-
impl<'a, F: Float + FromPrimitive + AddAssign + SubAssign> Univariate<F> for Rolling<'_, F> {
58+
impl<'a, U, F> Univariate<F> for Rolling<'a, U, F>
59+
where
60+
U: RollableUnivariate<F>,
61+
F: Float + FromPrimitive + AddAssign + SubAssign,
62+
{
5563
fn update(&mut self, x: F) {
5664
if self.window.len() == self.window_size {
5765
// To handle the error, the program panics because returning the error type would change
5866
// the interface of the get method. This problem is unlikely to happen because we
5967
// control the size of the sliding window in the constructor.
60-
match self.to_roll.revert(*self.window.front().unwrap()) {
61-
Ok(it) => it,
68+
let oldest = self.window.front().copied().expect("Window should not be empty"); // Optimization: copied() for clarity/safety (F is Copy-like for floats); expect for debug assert
69+
match self.to_roll.revert(oldest) {
70+
Ok(()) => (), // Assume revert returns Result<(), _>; adjust if different
6271
Err(err) => panic!("{}", err),
6372
};
6473
self.window.pop_front();
@@ -73,6 +82,7 @@ impl<'a, F: Float + FromPrimitive + AddAssign + SubAssign> Univariate<F> for Rol
7382
self.to_roll.get()
7483
}
7584
}
85+
7686
mod tests {
7787
#[test]
7888
fn it_works() {
@@ -82,10 +92,10 @@ mod tests {
8292
let data = vec![9., 7., 3., 2., 6., 1., 8., 5., 4.];
8393
let mut running_var: Variance<f64> = Variance::default();
8494
// We wrap `running_var` inside the `Rolling` struct.
85-
let mut rolling_var: Rolling<f64> = Rolling::new(&mut running_var, 2).unwrap();
95+
let mut rolling_var: Rolling<_, f64> = Rolling::new(&mut running_var, 2).unwrap(); // Note: _ for type inference
8696
for x in data.iter() {
8797
rolling_var.update(*x as f64);
8898
}
8999
assert_eq!(rolling_var.get(), 0.5);
90100
}
91-
}
101+
}

0 commit comments

Comments
 (0)