kumarUjjawal commented on code in PR #24476:
URL: https://github.com/apache/datafusion/pull/24476#discussion_r3832250229


##########
datafusion/physical-plan/src/aggregates/group_values/multi_group_by/dictionary.rs:
##########
@@ -546,6 +587,52 @@ impl<K: ArrowDictionaryKeyType + Send + Sync> GroupColumn
     }
 }
 
+/// Compact view buffers after `take` and subset nested dictionary values the 
same way.
+fn gc_taken_values(array: ArrayRef) -> ArrayRef {

Review Comment:
   `spill/mod.rs:423` already has a `gc_array` for this it recurses into nested
     children and gates `gc()` behind a size threshold, which matters since 
`gc()`
     never no-ops. As written this copies the string data on every `take_n` even
     when nothing is reclaimable.
   
     Worth reusing?



##########
datafusion/physical-plan/src/aggregates/group_values/multi_group_by/dictionary.rs:
##########
@@ -546,6 +587,52 @@ impl<K: ArrowDictionaryKeyType + Send + Sync> GroupColumn
     }
 }
 
+/// Compact view buffers after `take` and subset nested dictionary values the 
same way.
+fn gc_taken_values(array: ArrayRef) -> ArrayRef {
+    match array.data_type() {
+        DataType::Utf8View => Arc::new(array.as_string_view().gc()) as 
ArrayRef,
+        DataType::BinaryView => Arc::new(array.as_binary_view().gc()) as 
ArrayRef,
+        DataType::Dictionary(_, _) => {
+            let arr = array.as_ref();
+            downcast_dictionary_array!(
+                arr => gc_dictionary(arr),
+                _ => array
+            )
+        }
+        _ => array,
+    }
+}
+
+/// Keep only values referenced by keys then gc those values
+fn gc_dictionary<K: ArrowDictionaryKeyType>(array: &DictionaryArray<K>) -> 
ArrayRef {

Review Comment:
    Is this reachable? It needs `Dictionary(K1, Dictionary(K2, V))` and I 
couldn't
     find anything that produces one. If there is a path, it'd be worth a test 
since
     nothing exercises this today



##########
datafusion/physical-plan/src/aggregates/group_values/multi_group_by/dictionary.rs:
##########
@@ -515,28 +528,56 @@ impl<K: ArrowDictionaryKeyType + Send + Sync> GroupColumn
             new_to_old.push(old);
         }
 
-        self.value_dedup = HashTable::new();
-        self.value_dedup_size = 0;
-        self.null_inner_slot = None;
-
-        self.hash_values(&all_inner_values);
+        // Subset leftover values and gc view/nested buffers so dropped 
strings are freed
+        let leftover_non_null_len = new_to_old.len() - null_old_slot.is_some() 
as usize;
+        let leftover_values = if leftover_non_null_len == 0 {
+            None
+        } else {
+            let leftover_indices = Int64Array::from_iter(
+                new_to_old[..leftover_non_null_len]
+                    .iter()
+                    .map(|&i| i as i64),
+            );
+            let taken = take(&*all_inner_values, &leftover_indices, None)
+                .expect("take leftover values");
+            Some(gc_taken_values(taken))
+        };
 
+        // Drop fully-emitted slots from value_dedup and remap leftover slots 
in place
+        let old_hashes = std::mem::take(&mut self.slot_hash);
+        for &old in &emit_new_to_old {
+            if old_to_new[old] != usize::MAX {
+                continue;
+            }
+            let hash = old_hashes[old];
+            if let Ok(entry) = self.value_dedup.find_entry(hash, 
|&(entry_hash, slot)| {
+                entry_hash == hash && slot == old
+            }) {
+                entry.remove();
+            }
+        }
+        self.null_inner_slot = None;
+        self.slot_hash.resize(leftover_non_null_len, 0);

Review Comment:
   Am I right that this can alias? `*slot = new_slot` runs while later 
iterations still match on `slot == old_slot`, so two entries sharing a hash can 
end up swapped, silently grouping rows under the wrong key. Rare, but 
`value_dedup` does support collisions.



##########
datafusion/physical-plan/src/aggregates/group_values/multi_group_by/dictionary.rs:
##########
@@ -515,28 +528,56 @@ impl<K: ArrowDictionaryKeyType + Send + Sync> GroupColumn
             new_to_old.push(old);
         }
 
-        self.value_dedup = HashTable::new();
-        self.value_dedup_size = 0;
-        self.null_inner_slot = None;
-
-        self.hash_values(&all_inner_values);
+        // Subset leftover values and gc view/nested buffers so dropped 
strings are freed
+        let leftover_non_null_len = new_to_old.len() - null_old_slot.is_some() 
as usize;

Review Comment:
     The leftover `take` + `gc` looks redundant here, `append_val` already 
copies into the builder's own storage (`bytes_view.rs:221`), so the rebuilt 
`inner` was never holding the original allocation. That makes this an extra 
full copy  per `take_n`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to