RFR: 8319423: Improve Year.isLeap by checking divisibility by 16 [v2]

Cassio Neri duke at openjdk.org
Sat Nov 4 21:38:08 UTC 2023


On Sat, 4 Nov 2023 16:01:44 GMT, Claes Redestad <redestad at openjdk.org> wrote:

>> 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.
>
> Benchmark                           Mode  Cnt  Score   Error   Units
> LeapYearBench.isLeapYear           thrpt   15  0,735 ± 0,004  ops/us
> LeapYearBench.isLeapYearChrono     thrpt   15  0,734 ± 0,006  ops/us
> 
> 
> So equal to or even slightly worse than baseline. I tested a few variants before submitting the PR - some that looked simpler or better - but the ternary variant in this PR always came out on top.

On top of my general comments made earlier, let me give my two cents on this line:

  return (year & 15) == 0 ? (year & 3) == 0 : (year & 3) == 0 && year % 100 != 0;

If `(year & 15) == 0`, then the last *four* bits of `year` are zeros. In particular, the last *two* bits of `year` are zeros, that is, `(year & 3) == 0`. The same conclusion can be obtained in terms of divisibility: if `year % 16 == 0`, i.e., `year` is a multiple of 16, then `year % 4 == 0`, i.e., `year` is multiple of 4. What I'm trying to say is that the line above can be simplified to:

  return (year & 15) == 0 ? true : (year & 3) == 0 && year % 100 != 0;

But now it becomes clear that the above is also equivalent to:

  return (year & 15) == 0 || ((year & 3) == 0 && year % 100 != 0);

Which is the simplest form of all the above. It's possible, but I'm not sure, that the Java compiler makes this simplification for you. (FWIW: the GCC C compiler does. Indeed, as seen [here](https://godbolt.org/z/P8sMv3Yno) the three expressions above generate exactly the same assembly instructions.)

As I explained in my earlier post, for this particular expression a further simplification, that makes the compiler (at least the C compiler [above](https://godbolt.org/z/P8sMv3Yno)) to save one instruction (`ror edi 2`) is replacing 100 by 25:

  return (year & 15) == 0 || ((year & 3) == 0 && year % 25 != 0);

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

PR Review Comment: https://git.openjdk.org/jdk/pull/16491#discussion_r1382469177


More information about the core-libs-dev mailing list