On Thu, 13 Aug 2026 22:48:49 GMT, Chad Rakoczy <[email protected]> wrote:

> Adds support for vectorized dot product on aarch64 (`sdot` and `udot`) 
> through the Vector API
> 
> Dedicated dot product instructions have been shown to provide up to 10x 
> throughput for lucene 
> ([results](https://github.com/apache/lucene/pull/13572)) compared to 
> vectorized multiply and add. This PR adds two new methods to `ByteVector` to 
> leverage the aarch dot product instructions `sdot` and `udot` respectively.
> - `IntVector dot(Vector<Byte> v, Vector<Integer> acc)`
> - `IntVector dotUnsigned(Vector<Byte> v, Vector<Integer> acc)`
> 
> Each int lane of the result holds the dot product of the corresponding group 
> of four bytes from the two operands, added to the matching accumulator lane. 
> For example:
> 
> a = [a1, a2, a3, a4, ..., ..., a13, a14, a15, a16]
> b = [b1, b2, b3, b4, ..., ..., b13, b14, b15, b16]
> acc = [acc1, ..., ..., acc4]
> 
> a.dot(b, acc) -> [
>     acc1 + a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4, 
>     ..., 
>     ...,
>     acc4 + a13 * b13 + a14 * b14 + a15 * b15 + a16 * b16
> ]
> 
> 
> The equivalent instructions on x86 are `VPDPBSSD` and `VPDPBUUD` which 
> perform the same operations and match the proposed new functions however this 
> PR only includes aarch64.
> 
> Graviton 2
> 
> Benchmark                              Mode  Cnt      Score     Error   Units
> VectorDotBenchmark.dotScalar          thrpt   25   2412.719 ±   0.014  ops/ms
> VectorDotBenchmark.dotMulAdd          thrpt   25   1497.953 ±   2.734  ops/ms
> VectorDotBenchmark.dotVector          thrpt   25  23243.016 ± 141.708  ops/ms
> VectorDotBenchmark.dotUnsignedScalar  thrpt   25   2402.966 ±   0.157  ops/ms
> VectorDotBenchmark.dotUnsignedMulAdd  thrpt   25   1498.898 ±   1.442  ops/ms
> VectorDotBenchmark.dotUnsignedVector  thrpt   25  23344.116 ± 222.150  ops/ms
> 
> 
> Graviton 3
> 
> Benchmark                              Mode  Cnt      Score     Error   Units
> VectorDotBenchmark.dotScalar          thrpt   25   7946.950 ±   2.564  ops/ms
> VectorDotBenchmark.dotMulAdd          thrpt   25   3257.311 ±   5.308  ops/ms
> VectorDotBenchmark.dotVector          thrpt   25  41430.996 ± 688.936  ops/ms
> VectorDotBenchmark.dotUnsignedScalar  thrpt   25   2536.268 ±   0.438  ops/ms
> VectorDotBenchmark.dotUnsignedMulAdd  thrpt   25   3259.853 ±   3.511  ops/ms
> VectorDotBenchmark.dotUnsignedVector  thrpt   25  41263.821 ± 478.856  ops/ms
> 
> 
> ---------
> - [x] I confirm that I make this contribution in accordance with the [OpenJDK 
> Interim AI Policy](https://openjdk.org/legal/ai).

Hi,
I'd like to add a +1 for this with a concrete use case.
I also think having x86 support would be really cool.

I have been building for some time a pure Java media library that uses the 
Vector API for its codecs: AV1, HEVC, H.264, VP9 and VP8 decoding and encoding, 
WebP, AAC and so on. I've been benchmarking every codec throughout development 
against FFmpeg and dav1d.
I have plans to release this work soon, but I was trying to reach performance 
parity in as many workloads as possible against these implementations before 
doing so.
Most of the remaining gap comes from one thing: the Vector API has no integer 
multiply-add for narrow types.
Right now every byte-times-coefficient kernel widens the bytes, multiplies in 
wider lanes and adds, which costs 2 to 4 times the instructions of the native 
code.
What I think I would need is dot products over 8-bit pixels or 16-bit 
coefficients, so this PR is exactly the primitive I'm missing.

On the points raised above:
- I don't think dot product is a snowflake op, at least not in media code. 
Basically every motion compensation filter, transform and SATD/RD cost kernel 
in FFmpeg, dav1d and libvpx is built on `pmaddubsw`/`pmaddwd` on x86 or 
`sdot`/`udot`/`usdot` on Arm. x86 has had the 16-bit pair version (`pmaddwd`) 
since SSE2, so it's about as portable as integer SIMD gets.
- The scalar-returning `int dot(Vector<Byte>)` wouldn't work for my use case. A 
filter needs one group sum per output sample: each output pixel is the sum of 4 
or 8 taps times 4 or 8 pixels. So the current shape, with a group-wise 
`IntVector` result and an accumulator, is exactly what I need. Reducing to a 
scalar on every call would make it unusable for filters and transforms.
- I'd also be careful with recognizing the pattern in C2 instead. I already 
spend a lot of time writing kernels in the specific shapes C2 handles well; for 
example, range checks only get hoisted when the index has a constant scale, so 
I keep one kernel per block size. If the dot product only kicks in when I hit a 
matched IR shape, that's the same brittleness as auto-vectorization, which is 
the reason I'm using the Vector API in the first place. An explicit op with an 
exact fallback (widen, mul, add) is predictable.

A few things:
1. I think x86 support could be added. With AVX-VNNI / AVX512-VNNI this maps to 
`vpdpbusd` (and `vpdpbssd` / `vpdpbuud` on newer cores). On plain AVX2 a 
fallback through zero-extend + `vpmaddwd` would still beat what we can write 
today. It has to keep exact semantics, so not `pmaddubsw`, since that saturates.
2. I'm not sure whether this is covered, but mixed signedness (unsigned x 
signed) would be very important for some use cases, like video. Pixels are 
unsigned bytes and filter taps are signed, which is exactly what `vpdpbusd`, 
`pmaddubsw` and aarch64's `usdot` do. With only signed x signed and unsigned x 
unsigned we'd still have to widen or bias the pixels first.
3. I think a 16-bit variant (e.g. `ShortVector.dot` into `IntVector`, pairs of 
shorts per int lane) would also be very useful: this is `pmaddwd` on x86 
(`vpdpwssd` with VNNI), and it's what transforms and the second filter pass of 
motion compensation need.

Maybe framing these as one family, a widening dot over groups of 4 bytes or 2 
shorts into int lanes, would also make it feel less like a one-off operation.

I'm happy to test any of this on my codecs (x86 AVX2 and AVX-512 machines) and 
share before/after numbers if that helps the review.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/32359#issuecomment-6135439361

Reply via email to