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]