Skip to content

Commit 2758171

Browse files
author
Marshall Pierce
authored
Merge pull request #68 from jonhoo/iter-recorded-total-count
Rework iteration to avoid overflow
2 parents 4b89c58 + b9e12c9 commit 2758171

File tree

12 files changed

+1676
-599
lines changed

12 files changed

+1676
-599
lines changed

examples/cli.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,24 @@ fn quantiles<R: BufRead, W: Write>(mut reader: R, mut writer: W, quantile_precis
121121
let mut sum = 0;
122122
for v in hist.iter_quantiles(ticks_per_half) {
123123
sum += v.count_since_last_iteration();
124-
if v.quantile() < 1.0 {
124+
if v.quantile_iterated_to() < 1.0 {
125125
writer.write_all(
126126
format!(
127127
"{:12} {:1.*} {:1.*} {:10} {:14.2}\n",
128-
v.value(),
128+
v.value_iterated_to(),
129129
quantile_precision,
130130
v.quantile(),
131131
quantile_precision,
132132
v.quantile_iterated_to(),
133133
sum,
134-
1_f64 / (1_f64 - v.quantile())
134+
1_f64 / (1_f64 - v.quantile_iterated_to())
135135
).as_ref(),
136136
)?;
137137
} else {
138138
writer.write_all(
139139
format!(
140140
"{:12} {:1.*} {:1.*} {:10} {:>14}\n",
141-
v.value(),
141+
v.value_iterated_to(),
142142
quantile_precision,
143143
v.quantile(),
144144
quantile_precision,

src/iterators/all.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
11
use core::counter::Counter;
22
use Histogram;
3-
use iterators::{HistogramIterator, PickyIterator};
3+
use iterators::{HistogramIterator, PickMetadata, PickyIterator};
44

55
/// An iterator that will yield every bin.
6-
pub struct Iter { visited: Option<usize> }
6+
pub struct Iter {
7+
visited: Option<usize>,
8+
}
79

810
impl Iter {
911
/// Construct a new full iterator. See `Histogram::iter_all` for details.
1012
pub fn new<'a, T: Counter>(hist: &'a Histogram<T>) -> HistogramIterator<'a, T, Iter> {
11-
HistogramIterator::new(hist, Iter{ visited: None })
13+
HistogramIterator::new(hist, Iter { visited: None })
1214
}
1315
}
1416

1517
impl<T: Counter> PickyIterator<T> for Iter {
16-
fn pick(&mut self, index: usize, _: u64) -> bool {
18+
fn pick(&mut self, index: usize, _: u64, _: T) -> Option<PickMetadata> {
1719
if self.visited.map(|i| i != index).unwrap_or(true) {
1820
// haven't visited this index yet
1921
self.visited = Some(index);
20-
true
22+
Some(PickMetadata::new(None, None))
2123
} else {
22-
false
24+
None
2325
}
2426
}
2527

2628
fn more(&mut self, _: usize) -> bool {
2729
true
2830
}
29-
30-
fn quantile_iterated_to(&self) -> Option<f64> {
31-
None
32-
}
3331
}

src/iterators/linear.rs

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::counter::Counter;
22
use Histogram;
3-
use iterators::{HistogramIterator, PickyIterator};
3+
use iterators::{HistogramIterator, PickMetadata, PickyIterator};
44

55
/// An iterator that will yield at fixed-size steps through the histogram's value range.
66
pub struct Iter<'a, T: 'a + Counter> {
@@ -14,45 +14,54 @@ pub struct Iter<'a, T: 'a + Counter> {
1414

1515
impl<'a, T: 'a + Counter> Iter<'a, T> {
1616
/// Construct a new linear iterator. See `Histogram::iter_linear` for details.
17-
pub fn new(hist: &'a Histogram<T>,
18-
value_units_per_bucket: u64)
19-
-> HistogramIterator<'a, T, Iter<'a, T>> {
20-
assert!(value_units_per_bucket > 0, "value_units_per_bucket must be > 0");
21-
HistogramIterator::new(hist,
22-
Iter {
23-
hist,
24-
value_units_per_bucket,
25-
// won't underflow because value_units_per_bucket > 0
26-
current_step_highest_value_reporting_level: value_units_per_bucket - 1,
27-
current_step_lowest_value_reporting_level:
28-
hist.lowest_equivalent(value_units_per_bucket - 1),
29-
})
17+
pub fn new(
18+
hist: &'a Histogram<T>,
19+
value_units_per_bucket: u64,
20+
) -> HistogramIterator<'a, T, Iter<'a, T>> {
21+
assert!(
22+
value_units_per_bucket > 0,
23+
"value_units_per_bucket must be > 0"
24+
);
25+
HistogramIterator::new(
26+
hist,
27+
Iter {
28+
hist,
29+
value_units_per_bucket,
30+
// won't underflow because value_units_per_bucket > 0
31+
current_step_highest_value_reporting_level: value_units_per_bucket - 1,
32+
current_step_lowest_value_reporting_level: hist.lowest_equivalent(
33+
value_units_per_bucket - 1,
34+
),
35+
},
36+
)
3037
}
3138
}
3239

3340
impl<'a, T: 'a + Counter> PickyIterator<T> for Iter<'a, T> {
34-
fn pick(&mut self, index: usize, _: u64) -> bool {
41+
fn pick(&mut self, index: usize, _: u64, _: T) -> Option<PickMetadata> {
3542
let val = self.hist.value_for(index);
36-
if val >= self.current_step_lowest_value_reporting_level || index == self.hist.last_index() {
43+
if val >= self.current_step_lowest_value_reporting_level || index == self.hist.last_index()
44+
{
45+
let metadata =
46+
PickMetadata::new(None, Some(self.current_step_highest_value_reporting_level));
3747
self.current_step_highest_value_reporting_level += self.value_units_per_bucket;
3848
self.current_step_lowest_value_reporting_level = self.hist
3949
.lowest_equivalent(self.current_step_highest_value_reporting_level);
40-
true
50+
Some(metadata)
4151
} else {
42-
false
52+
None
4353
}
4454
}
4555

46-
fn more(&mut self, index: usize) -> bool {
56+
fn more(&mut self, index_to_pick: usize) -> bool {
4757
// If the next iterate will not move to the next sub bucket index (which is empty if
4858
// if we reached this point), then we are not yet done iterating (we want to iterate
49-
// until we are no longer on a value that has a count, rather than util we first reach
59+
// until we are no longer on a value that has a count, rather than until we first reach
5060
// the last value that has a count. The difference is subtle but important)...
51-
// TODO index + 1 could overflow 16-bit usize
52-
self.current_step_highest_value_reporting_level + 1 < self.hist.value_for(index + 1)
53-
}
54-
55-
fn quantile_iterated_to(&self) -> Option<f64> {
56-
None
61+
// When this is called, we're about to begin the "next" iteration, so
62+
// current_step_highest_value_reporting_level has already been incremented,
63+
// and we use it without incrementing its value.
64+
let next_index = index_to_pick.checked_add(1).expect("usize overflow");
65+
self.current_step_highest_value_reporting_level < self.hist.value_for(next_index)
5766
}
5867
}

src/iterators/log.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::counter::Counter;
22
use Histogram;
3-
use iterators::{HistogramIterator, PickyIterator};
3+
use iterators::{HistogramIterator, PickMetadata, PickyIterator};
44

55
/// An iterator that will yield at log-size steps through the histogram's value range.
66
pub struct Iter<'a, T: 'a + Counter> {
@@ -17,51 +17,58 @@ pub struct Iter<'a, T: 'a + Counter> {
1717

1818
impl<'a, T: 'a + Counter> Iter<'a, T> {
1919
/// Construct a new logarithmic iterator. See `Histogram::iter_log` for details.
20-
pub fn new(hist: &'a Histogram<T>,
21-
value_units_in_first_bucket: u64,
22-
log_base: f64)
23-
-> HistogramIterator<'a, T, Iter<'a, T>> {
24-
assert!(value_units_in_first_bucket > 0, "value_units_per_bucket must be > 0");
20+
pub fn new(
21+
hist: &'a Histogram<T>,
22+
value_units_in_first_bucket: u64,
23+
log_base: f64,
24+
) -> HistogramIterator<'a, T, Iter<'a, T>> {
25+
assert!(
26+
value_units_in_first_bucket > 0,
27+
"value_units_per_bucket must be > 0"
28+
);
2529
assert!(log_base > 1.0, "log_base must be > 1.0");
26-
HistogramIterator::new(hist,
27-
Iter {
28-
hist,
29-
log_base,
30-
next_value_reporting_level: value_units_in_first_bucket as f64,
31-
current_step_highest_value_reporting_level: value_units_in_first_bucket -
32-
1,
33-
current_step_lowest_value_reporting_level:
34-
hist.lowest_equivalent(value_units_in_first_bucket - 1),
35-
})
30+
HistogramIterator::new(
31+
hist,
32+
Iter {
33+
hist,
34+
log_base,
35+
next_value_reporting_level: value_units_in_first_bucket as f64,
36+
current_step_highest_value_reporting_level: value_units_in_first_bucket - 1,
37+
current_step_lowest_value_reporting_level: hist.lowest_equivalent(
38+
value_units_in_first_bucket - 1,
39+
),
40+
},
41+
)
3642
}
3743
}
3844

3945
impl<'a, T: 'a + Counter> PickyIterator<T> for Iter<'a, T> {
40-
fn pick(&mut self, index: usize, _: u64) -> bool {
46+
fn pick(&mut self, index: usize, _: u64, _: T) -> Option<PickMetadata> {
4147
let val = self.hist.value_for(index);
42-
if val >= self.current_step_lowest_value_reporting_level || index == self.hist.last_index() {
48+
if val >= self.current_step_lowest_value_reporting_level || index == self.hist.last_index()
49+
{
50+
let metadata =
51+
PickMetadata::new(None, Some(self.current_step_highest_value_reporting_level));
4352
// implies log_base must be > 1.0
4453
self.next_value_reporting_level *= self.log_base;
4554
// won't underflow since next_value_reporting_level starts > 0 and only grows
46-
self.current_step_highest_value_reporting_level = self.next_value_reporting_level as u64 - 1;
55+
self.current_step_highest_value_reporting_level =
56+
self.next_value_reporting_level as u64 - 1;
4757
self.current_step_lowest_value_reporting_level = self.hist
4858
.lowest_equivalent(self.current_step_highest_value_reporting_level);
49-
true
59+
Some(metadata)
5060
} else {
51-
false
61+
None
5262
}
5363
}
5464

55-
fn more(&mut self, next_index: usize) -> bool {
65+
fn more(&mut self, index_to_pick: usize) -> bool {
5666
// If the next iterate will not move to the next sub bucket index (which is empty if if we
5767
// reached this point), then we are not yet done iterating (we want to iterate until we are
5868
// no longer on a value that has a count, rather than util we first reach the last value
5969
// that has a count. The difference is subtle but important)...
60-
self.hist.lowest_equivalent(self.next_value_reporting_level as u64) <
61-
self.hist.value_for(next_index)
62-
}
63-
64-
fn quantile_iterated_to(&self) -> Option<f64> {
65-
None
70+
self.hist
71+
.lowest_equivalent(self.next_value_reporting_level as u64)
72+
< self.hist.value_for(index_to_pick)
6673
}
6774
}

0 commit comments

Comments
 (0)