andygrove opened a new pull request, #5806:
URL: https://github.com/apache/datafusion-comet/pull/5806

   ## Which issue does this PR close?
   
   Closes #5795.
   
   ## Rationale for this change
   
   `GetMapValue` (`m[k]`) and `element_at(<map>, k)` both serialise to the 
DataFusion `map_extract`
   UDF, and the planner then unwrapped its one-element list result with a 
second `ListExtract` pass.
   
   The diagnosis in the issue holds. `general_map_extract_inner` re-slices the 
query key and every
   candidate key into a fresh `ArrayRef` per comparison and compares them 
through `dyn Array`
   equality:
   
   ```rust
   let query_key = query_keys_array.slice(row_index, 1);
   let value_index = (0..len).find(|&i| keys.slice(start + i, 1).as_ref() == 
query_key.as_ref());
   ```
   
   so the lookup is O(rows x entries-per-row) allocations, and the constant key 
is not hoisted out of
   the row loop. That is why the map lookup was the one map operation where 
Comet lost to Spark.
   
   ## What changes are included in this PR?
   
   A native `SparkMapExtract`, registered under the same `map_extract` name so 
it overrides the
   DataFusion one, in `native/spark-expr/src/map_funcs/map_extract.rs`:
   
   - one Arrow `eq` over the whole batch of map entries (broadcasting the key 
when it is a constant),
     then a bitmask scan for each row's first match, then one `take` to gather 
the values;
   - it returns the matched value directly instead of a one-element list, so 
the `ListExtract` wrapper
     in `planner.rs` is no longer needed and that second pass goes away;
   - a sliced `MapArray` keeps its original entry offsets, so only the visible 
entry window is
     compared.
   
   Semantics are unchanged: the first matching entry wins, and a missing key, a 
`NULL` map row and a
   `NULL` lookup key all yield `NULL`. Key types whose Spark equality a native 
lookup cannot reproduce
   (floating point, non-default collations, complex keys) are still declined by 
`MapKeySupport`, so
   they never reach the kernel. Arrow's `eq` rejects nested key types, so 
DataFusion's element-wise
   comparison is kept as a backstop for those, and a lookup key whose runtime 
type is not the map's
   key type is now rejected rather than silently missing every row.
   
   ## How are these changes tested?
   
   **Existing tests.** `CometMapExpressionSuite` (25), 
`CometArrayExpressionSuite` (64),
   `CometExpressionSuite` (141) and the full `CometSqlFileTestSuite` (474, 
including
   `element_at_map.sql`, `element_at_map_ansi.sql`, 
`element_at_map_collation.sql` and
   `get_map_value.sql`) all pass.
   
   **New tests.** 13 Rust unit tests covering hit/miss, duplicate keys, `NULL` 
maps, `NULL` values,
   `NULL` and per-row lookup keys, sliced maps, empty input, a scalar map 
argument, non-string keys,
   the nested-key backstop, and the rejected argument types. Two new 
`CometMapExpressionSuite` cases
   cover the same two tricky paths end to end: `element_at` under a native 
`OFFSET` (sliced map) and
   `element_at`/`m[k]` with a per-row lookup key.
   
   **Kernel benchmark** (new `native/spark-expr/benches/map_extract.rs`, 
8192-row batches of
   `map<string, string>`, 10% `NULL` rows, 60 distinct keys, run against both 
implementations):
   
   | entries/map | case | `datafusion` | `comet` | speedup |
   | --- | --- | --- | --- | --- |
   | 2 | constant key | 1.873 ms | 41.8 us | 44.9x |
   | 8 | constant key | 6.181 ms | 110.4 us | 56.0x |
   | 32 | constant key | 18.85 ms | 376.8 us | 50.0x |
   | 2 | per-row key | 1.946 ms | 145.0 us | 13.4x |
   | 8 | per-row key | 6.390 ms | 470.7 us | 13.6x |
   | 32 | per-row key | 19.68 ms | 1.795 ms | 11.0x |
   
   **End to end**, reproducing the issue's shape (2,000,000 rows, `attrs 
map<string, string>`, 10%
   `NULL` maps, 0-6 entries each, 60 distinct keys, 400 distinct values; `noop` 
sink, median of 5
   after 2 warmups; Apple silicon, so the absolute numbers differ from the 
issue's Ryzen run):
   
   | query | Spark | Comet before | Comet after |
   | --- | --- | --- | --- |
   | `SELECT attrs FROM t` | 1098 ms | 716 ms | 856 ms |
   | `SELECT size(attrs) FROM t` | 291 ms | 214 ms | 210 ms |
   | `SELECT size(map_keys(attrs)) FROM t` | 289 ms | 246 ms | 214 ms |
   | `SELECT size(map_values(attrs)) FROM t` | 276 ms | 231 ms | 204 ms |
   | `SELECT element_at(attrs, 'a1') FROM t` | 506 ms | 278 ms | **211 ms** |
   | combined query from the issue | 602 ms | 307 ms | 273 ms |
   
   Taking `size(attrs)` as the control, as the issue does, the isolated cost of 
the lookup goes from
   **+64 ms to +1.5 ms**, and `element_at` is no longer the outlier among the 
map kernels. The
   Spark-arm numbers drifted about 15% between the two runs on this machine, so 
read the Comet columns
   against each other and against their own control rather than the ratios.
   


-- 
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