Rework checking if a year is a leap year (#60585) (#60790)

This way is faster, saving about 8% on the microbenchmark that rounds to
the nearest month. That is in the hot path for `date_histogram` which is
a very popular aggregation so it seems worth it to at least try and
speed it up a little.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nik Everett 2020-08-10 12:45:34 -04:00 committed by GitHub
parent 66b3e89482
commit dfd502f9ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 2 deletions

View File

@ -63,3 +63,29 @@ To get realistic results, you should exercise care when running benchmarks. Here
* Blindly believe the numbers that your microbenchmark produces but verify them by measuring e.g. with `-prof perfasm`.
* Run more threads than your number of CPU cores (in case you run multi-threaded microbenchmarks).
* Look only at the `Score` column and ignore `Error`. Instead take countermeasures to keep `Error` low / variance explainable.
## Disassembling
Disassembling is fun! Maybe not always useful, but always fun! Generally, you'll want to install `perf` and FCML's `hsdis`.
`perf` is generally available via `apg-get install perf` or `pacman -S perf`. FCML is a little more involved. This worked
on 2020-08-01:
```
wget https://github.com/swojtasiak/fcml-lib/releases/download/v1.2.2/fcml-1.2.2.tar.gz
tar xf fcml*
cd fcml*
./configure
make
cd example/hsdis
make
cp .libs/libhsdis.so.0.0.0
sudo cp .libs/libhsdis.so.0.0.0 /usr/lib/jvm/java-14-adoptopenjdk/lib/hsdis-amd64.so
```
If you want to disassemble a single method do something like this:
```
gradlew -p benchmarks run --args ' MemoryStatsBenchmark -jvmArgs "-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*.yourMethodName -XX:PrintAssemblyOptions=intel"
```
If you want `perf` to find the hot methods for you then do add `-prof:perfasm`.

View File

@ -92,8 +92,27 @@ class DateUtilsRounding {
return (year * 365L + (leapYears - DAYS_0000_TO_1970)) * MILLIS_PER_DAY; // millis per day
}
private static boolean isLeapYear(final int year) {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
static boolean isLeapYear(final int year) {
// Joda had
// return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
// But we've replaced that with this:
if ((year & 3) != 0) {
return false;
}
if (year % 100 != 0) {
return true;
}
return ((year / 100) & 3) == 0;
/*
* It is a little faster because it saves a division. We don't have good
* measurements for this method on its own, but this change speeds up
* rounding the nearest month by about 8%.
*
* Note: If you decompile this method to x86 assembly you won't see the
* division you'd expect from % 100 and / 100. Instead you'll see a funny
* sequence of bit twiddling operations which the jvm thinks is faster.
* Division is slow so it almost certainly is.
*/
}
private static final long AVERAGE_MILLIS_PER_YEAR_DIVIDED_BY_TWO = MILLIS_PER_YEAR / 2;

View File

@ -46,4 +46,14 @@ public class DateUtilsRoundingTests extends ESTestCase {
}
}
}
public void testIsLeapYear() {
assertTrue(DateUtilsRounding.isLeapYear(2004));
assertTrue(DateUtilsRounding.isLeapYear(2000));
assertTrue(DateUtilsRounding.isLeapYear(1996));
assertFalse(DateUtilsRounding.isLeapYear(2001));
assertFalse(DateUtilsRounding.isLeapYear(1900));
assertFalse(DateUtilsRounding.isLeapYear(-1000));
assertTrue(DateUtilsRounding.isLeapYear(-996));
}
}