RFR: 8319423: Improve Year.isLeap by checking divisibility by 16 [v2]
Quan Anh Mai
qamai at openjdk.org
Sat Nov 4 05:01:08 UTC 2023
On Fri, 3 Nov 2023 23:22:27 GMT, Claes Redestad <redestad at openjdk.org> wrote:
>> https://github.com/cassioneri/eaf suggest this code for leap year calculation:
>>
>> public static boolean isLeap(long year) {
>> int d = year % 100 != 0 ? 4 : 16;
>> return (year & (d - 1)) == 0;
>> }
>>
>> .. with a claim this would compile down to branchless, easily pipelined code.
>>
>> This doesn't currently happen with C2. In the meantime I think we can improve the current code in `Year.isLeap` and `IsoChronology.isLeapYear` by leveraging the fact that the `% 100` check is only needed if `(year & 15) != 0`:
>>
>>
>> public static boolean isLeap(long year) {
>> return (year & 15) == 0 ? (year & 3) == 0 : (year & 3) == 0 && year % 100 != 0;
>> }
>>
>>
>> Mac M1:
>>
>> Name Cnt Base Error Test Error Unit Change
>> LeapYearBench.isLeapYear 15 0,743 ± 0,009 0,994 ± 0,005 ops/us 1,34x (p = 0,000*)
>> LeapYearBench.isLeapYearChrono 15 0,748 ± 0,006 0,991 ± 0,003 ops/us 1,32x (p = 0,000*)
>> LeapYearBench.isLeapYearNS 15 0,558 ± 0,026 0,552 ± 0,033 ops/us 0,99x (p = 0,602 )
>> * = significant
>>
>>
>> Linux x64:
>>
>> Name Cnt Base Error Test Error Unit Change
>> LeapYearBench.isLeapYear 15 0.534 ± 0.001 0.765 ± 0.004 ops/us 1.43x (p = 0.000*)
>> LeapYearBench.isLeapYearChrono 15 0.535 ± 0.000 0.753 ± 0.040 ops/us 1.41x (p = 0.000*)
>> LeapYearBench.isLeapYearNS 15 0.352 ± 0.000 0.351 ± 0.001 ops/us 1.00x (p = 0.000*)
>> * = significant
>>
>>
>> 30% higher throughput on M1, 40% on x64. `isLeapYearNS` runs a variant of the code from https://github.com/cassioneri/eaf ported to java - perhaps the JIT can be improved to do whatever clang/gcc does here and achieve an even better speed-up.
>>
>> Testing: so far only java/time/tck/java/time locally, will run a few tiers before filing an enhancement and opening the PR for review.
>
> Claes Redestad has updated the pull request incrementally with one additional commit since the last revision:
>
> Apply similar optimization to GregorianCalendar, sun.util.calendar
src/java.base/share/classes/java/time/Year.java line 321:
> 319: // So for a year that's divisible by 4, checking that it's also divisible by 16
> 320: // is sufficient to determine it must be a leap year.
> 321: return (year & 15) == 0 ? (year & 3) == 0 : (year & 3) == 0 && year % 100 != 0;
I think `(year & 3) == 0 && ((year & 15) == 0) || (year % 25) != 0` would be better simply because the common path will be a little bit shorter.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/16491#discussion_r1382334608
More information about the core-libs-dev
mailing list