Jackie-Jiang opened a new pull request, #20249:
URL: https://github.com/apache/pinot/pull/20249

   ## Summary
   apache/pinot#20186 added bulk reads of primitive arrays 
(`PrimitiveArrays.read*`) and bulk writes into
   `PagedPinotOutputStream` (`writeInts` and the like). They copy through a 
big-endian typed view from
   `PrimitiveArrays.MIN_BULK_VALUES` (64) values up and go one value at a time 
below it, and a TODO asked to retune that
   threshold. This PR times both branches per type and direction on four CPUs 
and replaces the one threshold:
   - Reads: `PrimitiveArrays.MIN_BULK_READ_VALUES` is 128, for all four types.
   - Writes: `PagedPinotOutputStream.MIN_BULK_WRITE_VALUES` is 256, for 
`writeInts` and `writeFloats`.
   - `writeLongs` and `writeDoubles` always put one value at a time. A copy 
through a view was slower at every measured
     length, up to 4096, on three of the four CPUs, and for longs on the fourth.
   
   The written bytes and the values read are unchanged. The tests move their 
boundary cases to both sides of the new
   thresholds, through the constants. `PrimitiveArrays.MIN_BULK_VALUES` is 
removed: it came with #20186 and is in no release,
   but the binary compatibility check reports the removal. It is a compile-time 
constant, so code already compiled against
   it has the value inlined and keeps working.
   
   ## Measurements
   JMH 1.37 on Temurin 25.0.5: 3 forks of 3 × 1 s iterations after 2 × 1 s of 
warmup, average ns per call. The machines
   are an Apple M3 Max and three 4-vCPU GitHub Actions runners: an Intel Xeon 
6973P-C, an AMD EPYC 7763 and a Neoverse N2.
   The runners share their hosts, so their numbers are noisier than the M3's.
   
   Each benchmark runs one branch, copied verbatim from `PrimitiveArrays` or 
`PagedPinotOutputStream`, at a fixed length, so
   the two branches are compared directly rather than through the threshold. 
Reads come from a big-endian buffer held in a
   field, the way `CustomObject.getBuffer()` hands it to the deserializers. 
Writes go into a big-endian page, 4 bytes in. A
   buffer from a local `duplicate()`, as in 
`BenchmarkPrimitiveListDeserialize`, lets C2 scalar-replace it, and its
   per-value timings then flip between two compiled shapes from fork to fork 
(48 ns or 406 ns for 1024 ints on the M3), so
   that shape was not used.
   
   First measured length at which the view is at least as fast as one value at 
a time, on heap and direct buffers:
   
   | | Apple M3 Max | Intel Xeon 6973P-C | AMD EPYC 7763 | Neoverse N2 | 
Threshold |
   |---|---|---|---|---|---|
   | Long / double reads | 160–192 | 96 | 64 | 96 | 128 |
   | Int / float reads | 96–128 | 96 in slow launches | 32 in slow launches | 
heap 64–96 in slow launches, direct ~200 | 128 |
   | Int / float writes | 256–320 | 192–256 | 256–320 | 192–256 in slow 
launches | 256 |
   | Long / double writes | never | never | never | longs never, doubles from 
1024 | none |
   
   View time / one-value-at-a-time time at the new thresholds, heap; direct 
(below 1, the view is faster). A range means one
   value at a time ran at two speeds across forks; it spans the slow and the 
fast forks:
   
   | | Apple M3 Max | Intel Xeon 6973P-C | AMD EPYC 7763 | Neoverse N2 |
   |---|---|---|---|---|
   | Read 128 ints | 0.94; 0.91 | 0.64–2.60; 0.61 | 0.67; 0.65 | 0.46–2.40; 
1.29 |
   | Read 128 longs | 1.18; 1.20 | 0.84; 0.81 | 0.85; 0.86 | 0.76; 0.79 |
   | Read 128 floats | 0.92; 0.90 | 0.64–1.42; 0.68–1.44 | 0.66–1.47; 0.68–1.49 
| 0.46–1.25; 1.29 |
   | Read 128 doubles | 1.15; 1.14 | 0.82; 0.85 | 0.86; 0.86 | 0.77; 0.79 |
   | Write 256 ints | 1.11; 1.08 | 0.89; 0.98 | 1.01; 0.95 | 0.66; 0.63–0.99 |
   | Write 256 floats | 0.95; 0.96 | 0.93; 0.89 | 1.03; 0.96 | 0.63–1.05; 
0.58–0.97 |
   
   On a little-endian CPU the view swaps the bytes in a native call 
(`Unsafe.copySwapMemory0`) with a fixed cost of 19 to
   24 ns on the Intel and AMD runners, 35 to 40 ns on the Neoverse N2 and 30 to 
37 ns on the M3. One value at a time has
   no fixed cost but costs more per value, so the view wins once there are 
enough values to pay for its call. The server
   cores get there sooner than the M3 because one value at a time is slower on 
them: 0.43 to 0.59 ns per long or double
   read, against 0.29 to 0.32 ns on the M3.
   
   Long and double reads are stable across forks on every machine. The view 
catches up at 64 to 96 values on the servers
   and at 160 to 192 on the M3, and at 128 it is 14 to 24% faster on the 
servers and 14 to 20% slower on the M3.
   
   Int and float reads one value at a time ran at two very different speeds 
from one JVM launch to the next on the three
   runners (in 8 to 17 of the 20 lengths per type and runner), for example 19 
or 77 ns for 128 ints on the Intel runner,
   and 25 or 126 ns for 160 ints on the AMD runner, while the view's cost 
stayed the same. Against the slow launches the
   view wins from 32 to 96 values; against the fast ones it can still lose at 
512. No threshold suits both, so the reads
   share one. The M3 showed no such split. Direct buffers on the Neoverse N2 
are the one stable exception: there one int
   or float at a time is 29% faster than the view at 128, and the two even out 
near 200.
   
   Int and float writes cross at 192 to 256 on the Intel runner, 256 to 320 on 
the AMD runner and the M3, and 192 to 256
   against the slow launches on the Neoverse N2. At 256 the view is 2 to 11% 
faster on the Intel runner, within 5%
   either way on the AMD runner, at most 5% slower than the fast launches on 
the Neoverse N2, and 8 to 11% slower for ints
   on the M3.
   
   For longs and doubles, a copy through a view was slower than one value at a 
time at every length measured, up to 4096,
   on the Intel, AMD and M3 machines, up to 3.9 times as long for longs into 
heap pages on the Intel runner. On the
   Neoverse N2 it was slower for longs and 6 to 20% faster from 1024 doubles up.
   
   The M3 numbers above are from Temurin 25.0.5. The same runs on 25.0.3 gave 
the same crossovers, except that long and
   double reads caught up at 192 instead of 160.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


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