iemejia opened a new pull request, #3504:
URL: https://github.com/apache/parquet-java/pull/3504

   ### Rationale for this change
   
   `ByteStreamSplitValuesWriter` is the primary writer for 
`BYTE_STREAM_SPLIT`-encoded `FLOAT`, `DOUBLE`, `INT32`, and `INT64` columns. 
Each value goes through a hot path that performs both an unnecessary allocation 
and N single-byte virtual dispatches.
   
   For `FloatByteStreamSplitValuesWriter.writeFloat(float v)`:
   
   ```java
   super.scatterBytes(BytesUtils.intToBytes(Float.floatToIntBits(v)));
   ```
   
   `BytesUtils.intToBytes` allocates a fresh `byte[4]` on every call. 
`scatterBytes` then loops:
   
   ```java
   for (int i = 0; i < bytes.length; ++i) {
     this.byteStreams[i].write(bytes[i]);   // 
CapacityByteArrayOutputStream.write(int)
   }
   ```
   
   So **per value**: 1 `byte[4]` allocation + 4 single-byte virtual dispatches. 
For a 100k-value `FLOAT` page that is 100k allocations and 400k single-byte 
writes. `DOUBLE`/`LONG` are even worse (`byte[8]`, 800k single-byte writes).
   
   ### What changes are included in this PR?
   
   Two stacked changes in `ByteStreamSplitValuesWriter`:
   
   1. **Eliminate per-value allocation**: replace 
`super.scatterBytes(BytesUtils.intToBytes(v))` with `bufferInt(v)` / 
`bufferLong(v)` that perform the little-endian decomposition with bit shifts 
directly, no temporary `byte[]`.
   
   2. **Batch single-byte writes**: accumulate `BATCH_SIZE = 128` values in a 
small per-instance scratch buffer and flush them as `N` bulk `write(byte[], 
off, len)` calls (one per stream), replacing `BATCH_SIZE * elementSizeInBytes` 
single-byte virtual dispatches with `elementSizeInBytes` bulk writes per flush. 
The constant was chosen by sweeping 16/32/64/128/256/512/1024 — 128 is the 
sweet spot for `FLOAT` throughput while still capturing most of the 
`DOUBLE`/`LONG` gains.
   
   Pending values are included in `getBufferedSize()` (so page-sizing decisions 
remain correct) and flushed in `getBytes()`. `reset()` and `close()` clear 
pending state. Only the four numeric subclasses (Float/Double/Integer/Long) use 
the batching path; `FixedLenByteArrayByteStreamSplitValuesWriter` continues to 
use `scatterBytes(byte[])` since its values arrive as already-laid-out byte 
arrays.
   
   ### Benchmark
   
   New `ByteStreamSplitEncodingBenchmark` (100k values per invocation, JDK 18, 
JMH `-wi 5 -i 10 -f 3`, 30 samples per row):
   
   | Type   | Before  | After   | Δ              | Alloc B/op       |
   |--------|--------:|--------:|---------------:|------------------|
   | Float  | 15.08M  | 65.06M  | **+331% (4.3x)** | 33.27 → 9.27 (**-72%**) |
   | Double |  6.99M  | 49.48M  | **+608% (7.1x)** | 42.54 → 18.55 (**-56%**) |
   | Int    | 15.64M  | 68.13M  | **+335% (4.4x)** | 33.27 → 9.27 (**-72%**) |
   | Long   |  7.09M  | 53.23M  | **+651% (7.5x)** | 42.54 → 18.55 (**-56%**) |
   
   The remaining per-op allocation (~9 B/op for Int/Float, ~19 B/op for 
Long/Double) is the `BytesInput[]` returned by `getBytes()` and the streams' 
internal slabs, which are amortised across the page rather than per value.
   
   ### Are these changes tested?
   
   Yes. All 573 `parquet-column` tests pass; 51 BSS-specific tests pass (`mvn 
test -pl parquet-column -Dtest='*ByteStreamSplit*'`). No new test was added 
because behaviour is unchanged (covered by the existing round-trip and writer 
tests).
   
   ### Are there any user-facing changes?
   
   No. Only an internal writer optimization. No public API, file format, or 
configuration change.
   
   ### Closes #3503
   
   Part of a small series of focused performance PRs from work in 
[parquet-perf](https://github.com/iemejia/parquet-perf). Previous: #3494, 
#3496, #3500.


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