perf: avoid clone on serialize frequency items#144
Conversation
Signed-off-by: tison <wander4096@gmail.com>
Signed-off-by: tison <wander4096@gmail.com>
|
Nice, LGTM! Two small thoughts:
|
This is proposed by my Codex for the first time. But I reject it because an
This may be possible. But now the returned rows are sorted so it seems not to be suitable to return an iterator. If you mean we may change |
|
But Something like: index 49dce3b..6895e4a 100644
--- a/datasketches/src/frequencies/sketch.rs
+++ b/datasketches/src/frequencies/sketch.rs
@@ -57,17 +57,17 @@ pub enum ErrorType {
///
/// Each row includes an estimate and upper and lower bounds on the true frequency.
#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Row<T> {
- item: T,
+pub struct Row<'a, T> {
+ item: &'a T,
estimate: u64,
upper_bound: u64,
lower_bound: u64,
}
-impl<T> Row<T> {
+impl<'a, T> Row<'a, T> {
/// Returns the item value.
- pub fn item(&self) -> &T {
- &self.item
+ pub fn item(&self) -> &'a T {
+ self.item
}
/// Returns the estimated frequency.
@@ -378,10 +378,7 @@ impl<T: Eq + Hash> FrequentItemsSketch<T> {
/// let rows = sketch.frequent_items(ErrorType::NoFalseNegatives);
/// assert!(rows.iter().any(|row| *row.item() == 1));
/// ```
- pub fn frequent_items(&self, error_type: ErrorType) -> Vec<Row<T>>
- where
- T: Clone,
- {
+ pub fn frequent_items(&self, error_type: ErrorType) -> impl Iterator<Item = Row<'_, T>> {
self.frequent_items_with_threshold(error_type, self.offset)
}
@@ -407,13 +404,9 @@ impl<T: Eq + Hash> FrequentItemsSketch<T> {
&self,
error_type: ErrorType,
threshold: u64,
- ) -> Vec<Row<T>>
- where
- T: Clone,
- {
+ ) -> impl Iterator<Item = Row<'_, T>> {
let threshold = threshold.max(self.offset);
- let mut rows = vec![];
- for (item, count) in self.hash_map.iter() {
+ self.hash_map.iter().filter_map(move |(item, count)| {
let lower = count;
let upper = count + self.offset;
let include = match error_type {
@@ -421,16 +414,16 @@ impl<T: Eq + Hash> FrequentItemsSketch<T> {
ErrorType::NoFalsePositives => lower > threshold,
};
if include {
- rows.push(Row {
- item: item.clone(),
+ Some(Row {
+ item,
estimate: upper,
upper_bound: upper,
lower_bound: lower,
- });
+ })
+ } else {
+ None
}
- }
- rows.sort_by_key(|row| std::cmp::Reverse(row.estimate));
- rows
+ })
}
fn maybe_resize_or_purge(&mut self) {But this would drop the sorted character that requires users to sort on their own. I need to review the contract. |
Yeah agree, that is what I meant when I said it's probably not faster for large maps. For most map sizes the three scans should be cheap but sparse scans probably loose to collect-once.
Agree, I don't have a use case for my project right now either, just thought I mention it because I noticed it while going over the API once again |
cc @k3dom