On Tue, 30 Jun 2026 17:09:26 GMT, Andrew Haley <[email protected]> wrote:

>> The current organization is intentional. I considered carrying the 
>> lane-position weights across loop iterations, similar to the x86_64 
>> implementation, as well as the previous AArch64 implementation.
>> 
>> The previous AArch64 implementation worked well on some cores by creating 
>> multiple smaller multiplication chains to help hide multiply latency for 
>> long arrays. In my measurements on recent Arm Neoverse cores (for example, 
>> Neoverse V2 and N2), reducing each 16-element block to a scalar block hash 
>> and then performing the scalar Horner update exposed more independent work 
>> to the scheduler and produced better performance than carrying vector 
>> accumulators across loop iterations.
>> 
>> The horizontal reduction is relatively inexpensive on these cores and can 
>> overlap with the vector multiply/add work, making the per-block reduction a 
>> good tradeoff.
>
> But what's the point of exposing more independent work to the scheduler, when 
> that work doesn't do anything useful?
> 
> Do you have the version without the reduction each time so we can compare it 
> on other implementations?

Yes. I implemented that version for comparison.

I have two standalone AArch64/NEON P16 implementations:

reduce_loop: performs the horizontal reduction inside every 16-byte iteration.
reduce_end: keeps 16 logical accumulators (four NEON vectors) across the loop 
and performs a single reduction after processing all blocks (AArch64 current 
stub is a smarter(less vector registers) implementation of this algorithm too).

For this experiment, both implementations assume:

input length is a multiple of 16,
no tail handling,
seed = 0.

On my test system, both implementations produce the same result as the scalar 
reference for the tested lengths under those assumptions.

At 64 KB, I measured:

reduce_loop: 0.0976 ns/byte
reduce_end : 0.1316 ns/byte

reduce_loop was consistently faster across all tested lengths from 16 bytes up 
to 64 KB.

I've also included a small benchmark driver and build/run instructions so 
others can reproduce the comparison:
[hash.zip](https://github.com/user-attachments/files/29598153/hash.zip)
g++ -O3 -march=armv8-a -o hash_bench \

    driver.cpp \
    hash_jdk_neon_reduce_loop.S \
    hash_jdk_neon_reduce_end.S
./hash_bench

This is detailed output:
=== Correctness display only; mismatch is OK for this experiment ===
len=   16  scalar=0xCAD5BD38  loop=0xCAD5BD38  end=0xCAD5BD38
len=   32  scalar=0x20787A70  loop=0x20787A70  end=0x20787A70
len=   64  scalar=0xB196B3E0  loop=0xB196B3E0  end=0xB196B3E0
len=  128  scalar=0x5E42E8C0  loop=0x5E42E8C0  end=0x5E42E8C0
len=  256  scalar=0xF24DEF80  loop=0xF24DEF80  end=0xF24DEF80
len=  512  scalar=0x07ABDF00  loop=0x07ABDF00  end=0x07ABDF00
len= 1024  scalar=0x9B97BE00  loop=0x9B97BE00  end=0x9B97BE00
len= 4096  scalar=0x945EF800  loop=0x945EF800  end=0x945EF800
len=16384  scalar=0xB17BE000  loop=0xB17BE000  end=0xB17BE000
len=65536  scalar=0xC5EF8000  loop=0xC5EF8000  end=0xC5EF8000

#=== Performance ===

Implementation                len    iters      ns/call    ns/byte       MB/s
--------------------------------------------------------------------------------
scalar                         16    50000         7.03     0.4397    2274.46
reduce_loop                    16    50000         2.44     0.1526    6553.29
reduce_end                     16    50000         3.05     0.1908    5240.16

scalar                         32    50000        16.99     0.5309    1883.59
reduce_loop                    32    50000         3.97     0.1241    8060.90
reduce_end                     32    50000         4.28     0.1337    7478.56

scalar                         64    50000        43.93     0.6865    1456.76
reduce_loop                    64    50000         6.48     0.1012    9881.76
reduce_end                     64    50000         7.12     0.1112    8993.92

scalar                        128    50000       104.72     0.8181    1222.35
reduce_loop                   128    50000        12.74     0.0996   10044.02
reduce_end                    128    50000        13.54     0.1058    9451.67

scalar                        256    50000       223.33     0.8724    1146.27
reduce_loop                   256    50000        25.44     0.0994   10064.75
reduce_end                    256    50000        29.77     0.1163    8598.75

scalar                        512    50000       460.43     0.8993    1112.02
reduce_loop                   512    50000        50.87     0.0993   10065.77
reduce_end                    512    50000        64.19     0.1254    7976.02

scalar                       1024    20000       934.85     0.9129    1095.36
reduce_loop                  1024    20000       101.74     0.0994   10064.82
reduce_end                   1024    20000       133.00     0.1299    7699.32

scalar                       4096    20000      3782.10     0.9234    1083.00
reduce_loop                  4096    20000       408.89     0.0998   10017.40
reduce_end                   4096    20000       544.38     0.1329    7524.16

scalar                      16384     5000     15164.99     0.9256    1080.38
reduce_loop                 16384     5000      1629.30     0.0994   10055.84
reduce_end                  16384     5000      2187.44     0.1335    7490.04

scalar                      65536     5000     60574.87     0.9243    1081.90
reduce_loop                 65536     5000      6500.62     0.0992   10081.49
reduce_end                  65536     5000      8786.19     0.1341    7458.98

=== Summary at 64KB ===
Metric                   scalar       loop        end
--------------------------------------------------------
ns/byte                  0.9247     0.0992     0.1340
MB/s                     1081.4    10079.7     7463.5
speedup vs scalar           1.0        9.3        6.9
speedup vs loop               -       1.00       0.74

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

PR Review Comment: https://git.openjdk.org/jdk/pull/31674#discussion_r3513070118

Reply via email to