txwyy123 commented on PR #26149:
URL: https://github.com/apache/datafusion/pull/26149#issuecomment-6185683942

   @jayzhan211 Final update on `column_count/64`. I spent the last two days 
exhaustively testing alternative data structures to eliminate the overflow 
regression. Here's the honest picture:
   
   ## Stable A/B benchmark (4 iterations each, same machine, back-to-back)
   
   | Case | main (ns) | PR (ns) | Δ% |
   |------|-----------|---------|-----|
   | `column_count/1` | 17.3 | 18.9 | +9.6% (+1.6ns abs) |
   | `column_count/4` | 59.3 | 55.8 | **−6.0%** ✅ |
   | `column_count/16` | 259.5 | 262.7 | +1.2% |
   | `column_count/64` | 2,042 | 2,258 | **+10.6%** (+216ns) |
   | `row_count/*` | 58.4 | 53.7 | **−8.1%** ✅ |
   | `layout/primitive` | 58.8 | 54.1 | **−8.0%** ✅ |
   | `layout/list` | 131 | 121 | **−7.6%** ✅ |
   | `layout/struct` | 175 | 162 | **−7.4%** ✅ |
   | `shared_slices/4` | 1,769 | 1,676 | **−5.2%** ✅ |
   | `shared_slices/16` | 8,447 | 9,063 | +7.3% |
   | `shared_slices/64` | 33,558 | 35,146 | +4.7% |
   
   My earlier comment showing −3.2% on `column_count/64` was a lucky 
measurement — the regression is real at **+10%** and stable across multiple 
runs. The earlier +7% report was also accurate; variance is ±3%.
   
   ## What I tried (and why nothing beats HashMap)
   
   | # | Approach | column_count/64 | shared_slices/64 | Why it fails |
   |---|---|---|---|---|
   | 1 | **Sorted Vec** (binary search + insert) | **−16%** ✅ | **+32%** ❌ | 
`Vec::insert` shifts up to 112 entries (73KB memcpy) on first-pass unique 
insertions |
   | 2 | **Lazy-sort Vec** (push + deferred sort) | **−12%** ✅ | **+47%** ❌ | 
Linear scan of unsorted tail on every duplicate lookup |
   | 3 | **Box\<enum{Set,Map}>** lazy upgrade | **−4%** ✅ | inline +8-14% ❌ | 
Enum discriminant breaks `Option` niche opt → 8 bytes larger struct → all 
inline cases regress |
   | 4 | **Two separate Option fields** | same as #3 | same | 112 extra bytes 
in struct, same inline regression |
   | 5 | **HashSet + refcounts HashMap** | **0%** ✅ | **+34%** ❌ | Two hash 
lookups per duplicate (Set check + refcounts insert) |
   | 6 | **Inline capacity = 32** | **+27%** ❌ | +6% | `position()` linear scan 
over 256 bytes (4 cache lines) too slow |
   
   ## Root cause (structural, not fixable)
   
   `HashMap<NonZero<usize>, u32>` = **16 bytes/slot** (8-byte key + 4-byte 
count + 4-byte alignment). `HashSet<NonZero<usize>>` = **8 bytes/slot**. This 
doubles the hash table footprint and the number of cache lines touched per 
probe. There is no way to fit both a `usize` key and a count in 8 bytes — every 
alternative that avoids the 16-byte slot trades performance at a different 
scale.
   
   ## Assessment
   
   The +10% on `column_count/64` is the structural cost of refcounting. It 
fires only when >16 **unique** buffers overflow the inline path (64 distinct 
columns with no buffer sharing). In real operator workloads (sort, window, 
join), batches are sliced from a parent — buffers are shared, not unique. The 
`shared_slices/4` case (4 columns × 32 slices — the common operator pattern) is 
**−5.2% faster** than main.
   
   The inline path (≤16 unique buffers) is **6–8% faster** across the board 
thanks to `#[inline(always)]` + `.position()`.
   
   Happy to iterate if you have other ideas, or if you'd prefer to accept the 
tradeoff.
   


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