mohitgurav20 commented on PR #26000:
URL: https://github.com/apache/datafusion/pull/26000#issuecomment-6562970790

   @mkleen I've narrowed this PR down strictly to the 
`datafusion.optimizer.single_distinct_aggregation_prefer_memory` config option 
(defaulting to `false`, matching `main`). All previous automatic heuristic 
gating and plan regressions have been reverted. Aggregates with 
`DistinctHandling::Unsupported` (like `approx_median`) are still rewritten 
regardless of the flag to maintain correctness.
   Here are the benchmark results on the 4,000,000 row table across the 15 
queries you shared, running with 8 target partitions and using 
`PeakRecordingPool` to capture exact peak memory reservations:
   | Category | Query | `prefer_memory=false` (default) Time | 
`prefer_memory=false` Peak Mem | `prefer_memory=true` Time | 
`prefer_memory=true` Peak Mem | Peak Mem Delta | Speed Ratio |
   |---|---|---|---|---|---|---|---|
   | GROUP BY, 2000 groups | `count(DISTINCT int)` | 148.8 ms | 186.96 MB | 
214.9 ms | 169.32 MB | -9.4% | 1.44x |
   | GROUP BY, 2000 groups | `sum(DISTINCT int)` | 169.3 ms | 193.96 MB | 179.9 
ms | 76.66 MB | -60.5% | 1.06x |
   | GROUP BY, 2000 groups | `count(DISTINCT str)` | 157.3 ms | 226.28 MB | 
852.9 ms | 327.48 MB | +44.7% | 5.42x |
   | GROUP BY, 2000 groups | `avg(DISTINCT float)` | 155.4 ms | 193.81 MB | 
219.1 ms | 76.66 MB | -60.4% | 1.41x |
   | GROUP BY, 10 groups | `count(DISTINCT int)` | 171.7 ms | 194.15 MB | 242.1 
ms | 158.09 MB | -18.6% | 1.41x |
   | GROUP BY, 10 groups | `sum(DISTINCT int)` | 130.6 ms | 193.71 MB | 117.4 
ms | 46.17 MB | -76.2% | 0.90x |
   | GROUP BY, 10 groups | `count(DISTINCT str)` | 147.1 ms | 226.28 MB | 322.4 
ms | 247.07 MB | +9.2% | 2.19x |
   | Companion aggregates | `sum(y), sum(DISTINCT x)` | 208.9 ms | 226.28 MB | 
183.4 ms | 76.79 MB | -66.1% | 0.88x |
   | Companion aggregates | `count(*), count(DISTINCT x)` | 178.8 ms | 169.44 
MB | 189.8 ms | 169.44 MB | 0.0% | 1.06x |
   | Companion aggregates | `min(y), max(y), count(DISTINCT s)` | 200.0 ms | 
266.97 MB | 848.8 ms | 327.73 MB | +22.8% | 4.24x |
   | No GROUP BY | `count(DISTINCT int)` | 73.3 ms | 47.71 MB | 117.2 ms | 
67.50 MB | +41.5% | 1.60x |
   | No GROUP BY | `sum(DISTINCT int)` | 66.7 ms | 48.90 MB | 112.0 ms | 72.00 
MB | +47.2% | 1.68x |
   | No GROUP BY | `count(DISTINCT str)` | 141.1 ms | 95.87 MB | 475.8 ms | 
328.50 MB | +242.7% | 3.37x |
   | No GROUP BY | `count(DISTINCT smallint)` | 5.8 ms | 0.57 MB | 2.3 ms | 
0.07 MB | -87.7% | 0.40x |
   | No GROUP BY | `count(*), count(DISTINCT x)` | 110.4 ms | 72.00 MB | 106.0 
ms | 72.00 MB | 0.0% | 0.96x |
   ### Summary of the Trade-offs
   1. **Where `prefer_memory = true` wins (60% to 76% memory savings):**
      - For `GROUP BY` queries with numeric aggregates (`sum(DISTINCT int)`, 
`avg(DISTINCT float)`, `sum(y), sum(DISTINCT x)`), skipping the two-phase 
rewrite drops peak memory by **60% to 76%** (~194 MB down to ~46–76 MB).
      - Because native numeric accumulators have low per-group overhead, 
runtime remains roughly at parity or even slightly faster (0.88x–1.06x) since 
we avoid the repartitioning and intermediate grouping table shuffle.
   2. **Where the two-phase rewrite is better (why default `false` makes 
sense):**
      - **Strings**: For variable-length data (`count(DISTINCT str)`), skipping 
the two-phase rewrite forces all distinct strings into the final stage 
accumulators without partial reduction, increasing peak memory (+9% to +45%) 
and slowing down queries by 2.2x–5.4x.
      - **No GROUP BY**: For queries without grouping keys, the rewrite is the 
only way DataFusion parallelizes distinct work across multiple threads. 
Suppressing it serializes accumulation into a single thread, resulting in a 
1.6x–3.4x slowdown on high-cardinality data.
      - **Low cardinality (`count(DISTINCT smallint)`):** On small distinct 
domains (100 distinct values), single-phase is both **88% lighter** (0.07 MB vs 
0.57 MB) and **2.5x faster** (2.3 ms vs 5.8 ms) because the 2-phase overhead is 
skipped completely.
   Overall, the numbers support keeping `false` as the default on `main` to 
preserve general multi-threaded throughput, while giving memory-constrained 
workloads an opt-in knob to cut peak memory by up to 76%.
   Also fixed a rustdoc intra-doc link issue on `DistinctHandling` that caused 
the earlier cargo doc check failure on the fork.


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