Problem
The Java batch tier of the rolling min/max family (MIN, MAX, MINMAX, MIDPOINT, MIDPRICE, WILLR, the #147 Van Herk block scan) runs 6 to 9x slower than C on a 100k-bar random walk. MFI shows the same signature at 3x. Across the corpus Java batch is 1.13x C at the median, so these rows are the tail a Java user would notice, and the only place a JNI wrapper of the C library would beat the pure-Java jar.
Measurements
Host: laptop, AMD Ryzen 7 PRO 8840U (Zen 4), WSL2, OpenJDK 21.0.12. Not yet measured on the Intel devbox. C is the shipped CMake Release libta-lib.so; Java is the public Core tier from dev 745247fe5. One function per process, min of 25 rounds, best of 3 processes, period 14. Both arms produce identical output.
ns/bar:
| function |
C 1k |
Java 1k |
Java/C |
C 100k |
Java 100k |
Java/C |
| MIN |
0.74 |
2.48 |
3.3 |
0.79 |
7.31 |
9.3 |
| MAX |
0.80 |
2.20 |
2.8 |
0.79 |
7.43 |
9.4 |
| MINMAX |
1.52 |
4.02 |
2.6 |
1.64 |
14.01 |
8.6 |
| MIDPOINT |
1.52 |
3.97 |
2.6 |
1.59 |
13.53 |
8.5 |
| MIDPRICE |
1.52 |
4.06 |
2.7 |
1.63 |
14.03 |
8.6 |
| WILLR |
2.45 |
4.52 |
1.8 |
2.46 |
15.29 |
6.2 |
| MFI |
3.77 |
3.82 |
1.0 |
3.74 |
11.39 |
3.1 |
| SMA (control) |
1.27 |
1.28 |
1.0 |
1.34 |
1.33 |
1.0 |
| BBANDS (control) |
3.27 |
3.64 |
1.1 |
3.38 |
3.61 |
1.1 |
What the numbers say about the cause
- C's cost per bar is the same at both sizes. Java's triples from 1k to 100k. A 1k series replayed thousands of times lets the branch predictor memorize it; 100k bars cannot be memorized. So the gap is data-dependent control flow in the C2 output, where GCC compiles the same min/max to branch-free code. A benchmark on a short repeated series understates this gap by about 3x.
- Input shape confirms it (MIN, 100k): Java is 8.7 ns/bar on a random walk and 2.6 to 3.6 on constant or monotone input.
- Spelling alone does not fix it.
a < b ? a : b and -XX:+UseCMoveUnconditionally both measure the same as the current code. Math.min reaches 4.9 to 5.8 ns/bar on a random walk and still gets faster on monotone input (1.9), so C2 is not branch-free there either.
Math.min is not a drop-in anyway: it propagates NaN and orders -0.0 below 0.0, where the C min keeps the incumbent. Outputs must stay bit-identical to C.
- The streaming rescan does not help: Java
MinStream.update is 9.1 ns/bar on the same input.
- A residual ~3x remains even with perfect prediction (2.6 vs 0.8 ns/bar), with an unidentified cause.
Deliverables
- The C2 disassembly of
MIN_Impl (hsdis) on a random walk, identifying what the hot loops pay for: branches, range checks, or missed vectorization.
- A Java emission for the seven functions that gets the 100k random walk within 2x of C (proposed target), bit-identical to C including NaN and signed-zero inputs (
--xlang-hash stays bitwise).
- No regression at 1k bars or on the constant/monotone shapes.
Not this mechanism
SAR/SAREXT (1.3 to 1.4x) and TRANGE (~1.9x) are slower at both sizes alike, so the size-dependent signature above does not apply. Rows in the corpus-wide ta_bench pass that did not reproduce in isolation: IMI (1.15x), AD (1.0x). DIV, SQRT and TYPPRICE run about 2x faster in Java than the shipped .so.
Problem
The Java batch tier of the rolling min/max family (MIN, MAX, MINMAX, MIDPOINT, MIDPRICE, WILLR, the #147 Van Herk block scan) runs 6 to 9x slower than C on a 100k-bar random walk. MFI shows the same signature at 3x. Across the corpus Java batch is 1.13x C at the median, so these rows are the tail a Java user would notice, and the only place a JNI wrapper of the C library would beat the pure-Java jar.
Measurements
Host: laptop, AMD Ryzen 7 PRO 8840U (Zen 4), WSL2, OpenJDK 21.0.12. Not yet measured on the Intel devbox. C is the shipped CMake Release
libta-lib.so; Java is the publicCoretier from dev745247fe5. One function per process, min of 25 rounds, best of 3 processes, period 14. Both arms produce identical output.ns/bar:
What the numbers say about the cause
a < b ? a : band-XX:+UseCMoveUnconditionallyboth measure the same as the current code.Math.minreaches 4.9 to 5.8 ns/bar on a random walk and still gets faster on monotone input (1.9), so C2 is not branch-free there either.Math.minis not a drop-in anyway: it propagates NaN and orders -0.0 below 0.0, where the C min keeps the incumbent. Outputs must stay bit-identical to C.MinStream.updateis 9.1 ns/bar on the same input.Deliverables
MIN_Impl(hsdis) on a random walk, identifying what the hot loops pay for: branches, range checks, or missed vectorization.--xlang-hashstays bitwise).Not this mechanism
SAR/SAREXT (1.3 to 1.4x) and TRANGE (~1.9x) are slower at both sizes alike, so the size-dependent signature above does not apply. Rows in the corpus-wide
ta_benchpass that did not reproduce in isolation: IMI (1.15x), AD (1.0x). DIV, SQRT and TYPPRICE run about 2x faster in Java than the shipped.so.