Feat: add dictionaries as a supported group column type#23187
Feat: add dictionaries as a supported group column type#23187Rich-T-kid wants to merge 22 commits into
Conversation
2b60132 to
1ffc7f0
Compare
3f7ff57 to
e6b6dce
Compare
|
@kumarUjjawal could you run the dictionary benchmarks on this PR? Thx |
| } | ||
| } | ||
| DataType::Dictionary(key_dt, value_dt) => { | ||
| let new_field = Field::new("", *value_dt.clone(), true); |
There was a problem hiding this comment.
Since this field is never read again it may be fine to ignore the name field.
should be weary of similar issues to #21765 (comment)
|
@kumarUjjawal wanted to bump this 😄 |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (eb41915) to 01bf68c (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
eb41915 to
770abfe
Compare
770abfe to
243a557
Compare
|
@codex review |
@Rich-T-kid Thank you! I have been sick so I won't be available for review. I will probably get back next week. |
152c1f0 to
f3387c5
Compare
|
@geoffreyclaude could you run the benchmarks command again when you get a chance. Thanks 🚀 |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (3d1e1c9) to 01bf68c (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
Rich-T-kid
left a comment
There was a problem hiding this comment.
Revision #4 Last revision before making this open for review
| cached_combined: Option<ArrayRef>, | ||
| /// Per-call group equality cache for the low-cardinality path. `Mutex` | ||
| /// because `vectorized_equal_to` takes `&self`; always uncontended. | ||
| group_eq_cache: Mutex<Vec<Option<bool>>>, |
There was a problem hiding this comment.
This is a work around needed because vectorized_equal_to takes in a &self as opposed to &mut self. This means the alternative is needing to allocate target_batch_size arrays for each intern() call instead of re-using the vector.
Originally planned to use RefCell but its not Send + Sync
There was a problem hiding this comment.
this shouldn't incur any overhead since there no contention but I'm happy to explore other ideas.
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
|
🤔 benchmarks show good improvement in every case but it should be much larger. Currently every |
f1391b9 to
5f03e49
Compare
|
more test that cover the changes of this PR are #23280 |
d4ea0f9 to
894a0a6
Compare
| pub struct DictionaryGroupValuesColumn<K: ArrowDictionaryKeyType + Send + Sync> { | ||
| inner: Box<dyn GroupColumn>, | ||
| null_array: ArrayRef, | ||
| // Mutex is required because `vectorized_equal_to` takes `&self` (GroupColumn trait |
There was a problem hiding this comment.
RefCell doesn't work here because its not sync
There was a problem hiding this comment.
std::sync::RwLock may also be an option
|
PR is ready for review 🚀 |
Did you run them yourself locally? What are your results? |
@geoffreyclaude yes. These are the results compared to main |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (894a0a6) to 01bf68c (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
| let unique_lhs_row_count = | ||
| lhs_rows.iter().collect::<hashbrown::HashSet<_>>().len(); | ||
| if unique_lhs_row_count <= rhs_rows.len() / CACHE_USE_THRESHOLD { | ||
| let mut comparison_cache = self.key_to_group_cache.lock().unwrap(); | ||
| for (position, (&lhs_row, &rhs_row)) in | ||
| lhs_rows.iter().zip(rhs_rows).enumerate() | ||
| { | ||
| if !equal_to_results.get_bit(position) { | ||
| continue; | ||
| } | ||
| let dict_key = dict.key(rhs_row); | ||
| let is_equal = *comparison_cache | ||
| .entry((lhs_row, dict_key)) | ||
| .or_insert_with(|| match dict_key { | ||
| None => self.inner.equal_to(lhs_row, &self.null_array, 0), | ||
| Some(key) => self.inner.equal_to(lhs_row, dict_values, key), | ||
| }); |
There was a problem hiding this comment.
This is causing more overhead than its saving.
There was a problem hiding this comment.
hashset insertions + mutext lock + hashsing two usize's
O(size * hash) O(1) O(~ unqiue values)
There was a problem hiding this comment.
I think the best thing to do here to to avoid any regressions. That means making this implementation a thin wrapper over an inner GroupColumns implementation. a follow up PR can try to optimize the low-cardinality case similar to #21765





Which issue does this PR close?
Rationale for this change
This PR introduces a specialized
GroupColumnimplementation for dictionary-typed columns insideGroupValuesColumn, allowing dictionary columns to participate in the columnar, vectorized aggregation path instead of the row-based fallback.The Implementation is only about 175+ lines of code. the remaining LOC is adding extensive test at the
GroupColumntrait level as well as testing theGroupValuesColumnGroupValues trait and how it inter-opts with multi-dictionary group by's.What changes are included in this PR?
DictionaryGroupValueBuilderstruct implementing theGroupColumntrait forDictionary-typed group-by columns, supporting a configurable subset of value typesGroupValuesColumn::try_new(thematches!block) to acceptDictionary(_, value_type)wherevalue_typeis already supported.emitAre these changes tested?
yes. a majority of this PR is test
Are there any user-facing changes?
no. this is a pure perf boost for users.