david-mollitor-db opened a new pull request, #59197:
URL: https://github.com/apache/spark/pull/59197

   ### What changes were proposed in this pull request?
   
   `SizeEstimator.visitArray` estimates an object array with more than 400 
elements from two samples of 100 elements and extrapolates the rest:
   
   ```scala
   state.size += math.max(s1, s2) +
     (size * ((length - ARRAY_SAMPLE_SIZE) / ARRAY_SAMPLE_SIZE))
   ```
   
   `length` and `ARRAY_SAMPLE_SIZE` are both `Int`, so `(length - 
ARRAY_SAMPLE_SIZE) / ARRAY_SAMPLE_SIZE` truncates before the multiplication and 
up to 99 elements are left out of the estimate. This PR multiplies first, `size 
* (length - ARRAY_SAMPLE_SIZE) / ARRAY_SAMPLE_SIZE`, which is evaluated in 
`Long` because `size` is a `Long`.
   
   ### Why are the changes needed?
   
   `MemoryStore` weighs a deserialized cached block with 
`SizeEstimator.estimate` over the block's `Array[T]` of rows, so every block 
whose row count is not a multiple of 100 is under-reported. For example, a 
499-element array is estimated as if it had 400 elements. The truncation dates 
back to SPARK-6738.
   
   Measured against JOL's `GraphLayout.totalSize()` (itself checked against 
`Instrumentation.getObjectSize`) on JDK 25 with Spark's executor `--add-opens`, 
estimate / actual for one partition array of rows:
   
   | Row shape | 450 rows, before | 450 rows, after | 499 rows, before | 499 
rows, after |
   |---|---|---|---|---|
   | `Tuple3[Int, Long, Double]` | 0.894 | 1.000 | 0.810 | 1.000 |
   | class with a `String` field | 0.888 | 0.994 | 0.809 | 0.999 |
   | `Row(Long, String, Double, Int, Timestamp, Boolean)` | 0.893 | 1.002 | 
0.805 | 0.998 |
   | nested `List` / `Map` / `int[]` | 0.875 | 0.983 | 0.771 | 0.954 |
   | `UnsafeRow` copies | 0.891 | 0.998 | 0.809 | 1.000 |
   
   Lengths that are multiples of 100 are unaffected. The error is bounded by 
99/N of the elements' size, so it matters most for arrays from 401 up to a few 
thousand elements.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No API change. Size estimates for object arrays longer than 400 elements 
whose length is not a multiple of 100 grow by up to 99 elements' worth, which 
is closer to their actual footprint. This slightly changes memory accounting 
for cached blocks and for size-tracked collections built on `SizeEstimator` 
(e.g. spill thresholds).
   
   ### How was this patch tested?
   
   - Added `sampled object arrays are extrapolated to their full length` to 
`SizeEstimatorSuite`, asserting exact sizes for arrays of 401, 450, 499 and 
1099 elements. Without the fix it fails with `Expected 11248, but got 11224`.
   - Passed: `SizeEstimatorSuite`, `SizeTrackerSuite`, `PrimitiveVectorSuite`, 
`OpenHashSetSuite`, `OpenHashMapSuite`, `MemoryStoreSuite` (core) and 
`ApproximatePercentileSuite` (catalyst).
   - The JOL comparison above was run with a local harness that is not part of 
this PR.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5.5)
   
   This pull request and its description were written by Isaac.
   


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