Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of parsing simple dates #8386

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 65 additions & 40 deletions domain/src/main/java/org/fao/geonet/domain/ISODate.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ public String getDateAndTime() {
}
}


public void setDateAndTime(String isoDate) {

String timeAndDate = isoDate;
Expand Down Expand Up @@ -370,58 +371,82 @@ public boolean equals(Object obj) {

private void parseDate(@Nonnull String isoDate) {
try {
String[] parts = isoDate.split("[-/]");
if ((parts.length == 0) || (parts.length > 3)) {
throw new IllegalArgumentException("Invalid ISO date: " + isoDate);
}
int startPos = 0;
int partNumber = 0;
int year = -1;
int month = -1;
int day = -1;

_shortDate = (parts.length == 3);
_shortDateYearMonth = (parts.length == 2);
_shortDateYear = (parts.length == 1);

int year;
if (parts[0].length() < 4) {
int shortYear = Integer.parseInt(parts[0]);
String thisYear = String.valueOf(ZonedDateTime.now(ZoneOffset.UTC).getYear());
int century = Integer.parseInt(thisYear.substring(0, 2)) * 100;
int yearInCentury = Integer.parseInt(thisYear.substring(2));

if (shortYear <= yearInCentury) {
year = century + shortYear;
} else {
year = century - 100 + shortYear;
ZoneId offset = ZoneId.systemDefault();
// canonicalize the string
isoDate = isoDate.replace('/', '-');
// until we've processed the whole string
while (startPos < isoDate.length()) {
if (partNumber >= 3) {
break;
}
} else {
year = Integer.parseInt(parts[0]);
// try to find the next chunk
int nextPos = isoDate.indexOf('-', startPos);
if (nextPos == -1) {
// no next chunk to be found? This means this is the last chunk, process it accordingly
nextPos = isoDate.length();
}
String subString = isoDate.substring(startPos, nextPos);
switch (partNumber) {
case 0:
// First part: year
int parsedInt = Integer.parseInt(subString);
if ((nextPos - startPos) < 4) {
int thisYear = ZonedDateTime.now(ZoneOffset.UTC).getYear();
int century = thisYear / 100;
int yearInCentury = thisYear % 100;
year = (century * 100) + parsedInt;
if (parsedInt > yearInCentury) {
// If year is 2024, turn 32-05-05 into 1932, not 2032
year -= 100;
}
} else {
year = parsedInt;
}
break;
case 1:
// Second part: month
month = Integer.parseInt(subString);
break;
case 2:
// Third part: day
if (subString.toLowerCase().endsWith("z")) {
offset = ZoneOffset.UTC;
day = Integer.parseInt(subString.substring(0, subString.length() - 1));
} else {
day = Integer.parseInt(subString);
}
break;
default:
throw new IllegalStateException("Should not reach partNumber " + partNumber);
}
partNumber++;
startPos = nextPos + 1;
}
if (partNumber == 0 || partNumber > 3) {
throw new IllegalArgumentException("Invalid ISO date: " + isoDate);
}

_shortDate = (partNumber == 3);
_shortDateYearMonth = (partNumber == 2);
_shortDateYear = (partNumber == 1);

int month;
if (_shortDate || _shortDateYearMonth) {
month = Integer.parseInt(parts[1]);
} else {
if (!_shortDate && !_shortDateYearMonth) {
month = 12;
}

int day;
ZoneId offset = ZoneId.systemDefault();
if (_shortDate) {

if (parts[2].toLowerCase().endsWith("z")) {
offset = ZoneOffset.UTC;
day = Integer.parseInt(parts[2].substring(0, parts[2].length() - 1));
} else {
day = Integer.parseInt(parts[2]);
}
} else {
if (!_shortDate) {
// Calculate the last day for the year/month
day = YearMonth.of(year, month).atEndOfMonth().getDayOfMonth();
}

_shortDate = true;
internalDateTime = ZonedDateTime.now(offset).withYear(year).withMonth(month).withDayOfMonth(day).withHour(0).withMinute(0)
.withSecond(0).withNano(0);
//..ZonedDateTime.of(year, month, day, hour, minute, second, 0, offset);

internalDateTime = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, offset);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid ISO date: " + isoDate, e);
}
Expand Down
1 change: 1 addition & 0 deletions jmh/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dependency-reduced-pom.xml
22 changes: 22 additions & 0 deletions jmh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Geonetwork JMH Benchmark Suite

This module contains micro benchmarks for some GN functions.
It is used to validate the performance of individual functions and snippets of code.
For performance tests at a larger scale, consider using another tool like jmeter

To get started using JMH, see the [JMH docs](https://github.com/openjdk/jmh)

## Adding new benchmarks

New benchmark can be added by simply
1. Adding the module of the class to test to the gn-jmh module
2. Writing a benchmark in this module

## Running the benchmarks

1. Make sure the `jmh` profile is enabled (or enable it using `-Pjmh` when running maven)
2. Run `mvn verify` to build the benchmarks
3. Run the benchmark using `java -jar jmh/target/benchmarks.jar` in this module

If you want to get additional inside, you can append `--prof stack`, which outputs text-base stack sampling, or `--prof jfr` to get java flight recorder profile that can be read by applications like VisualVM.
Make sure to only use additional profilers when analysing the data, not when actually recording numbers.
93 changes: 93 additions & 0 deletions jmh/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>geonetwork</artifactId>
<groupId>org.geonetwork-opensource</groupId>
<version>4.4.6-SNAPSHOT</version>
</parent>

<artifactId>gn-jmh</artifactId>
<packaging>jar</packaging>
<name>JMH benchmarks for Geonetwork</name>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>gn-domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<jmh.version>1.37</jmh.version>

<uberjar.name>benchmarks</uberjar.name>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<compilerArgs>
<arg>-processor</arg>
<arg>org.openjdk.jmh.generators.BenchmarkProcessor</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<!--
Shading signed JARs will fail without this.
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
-->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
23 changes: 23 additions & 0 deletions jmh/src/main/java/org/fao/geonet/domain/ISODateBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.fao.geonet.domain;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class ISODateBenchmark {
@Param({"1976-06-03", "1976/06/03", "24-06-06"})
public String arg;

@Benchmark
public void measureIsoSimple(Blackhole bh) {
ISODate isoDate = new ISODate(arg);
bh.consume(isoDate);
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,12 @@
<module>jmeter</module>
</modules>
</profile>
<profile>
<id>jmh</id>
<modules>
<module>jmh</module>
</modules>
</profile>
<profile>
<id>macOS</id>
<activation>
Expand Down
Loading