Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
27174f4
rebase & resolve DecimalDigits delete and add
wenshao Sep 11, 2023
93ed81d
little-endian
wenshao Sep 12, 2023
f4190a8
improve date toString performance, includes:
wenshao Sep 9, 2023
9bb6231
fix LocalDate.getChars offset
wenshao Sep 11, 2023
c6ab251
move java.util.DecimalDigits to jdk.internal.util.DecimalDigits
wenshao Sep 10, 2023
a849b2e
none static import
wenshao Sep 10, 2023
f05fb42
update related comments
wenshao Sep 11, 2023
72ea933
remove duplicate stringSize
wenshao Sep 11, 2023
c26eb70
move java.lang.StringLatin1::getChars to jdk.internal.util.DecimalDig…
wenshao Sep 11, 2023
342bbd2
base PR #15651 API
wenshao Sep 11, 2023
b26cb85
rebase PR #15651 last version
wenshao Sep 12, 2023
70566a7
revert un-related changes
wenshao Sep 12, 2023
2a617db
revert un-related changes
wenshao Sep 12, 2023
26d7c7c
revert un-related changes
wenshao Sep 12, 2023
5d0fa13
improved ISO_INSTANT format
wenshao Sep 12, 2023
8b3f063
improved DateTimeFormatter.format
wenshao Sep 12, 2023
6288ee9
Merge branch 'master' into optim_for_date_to_string_2
wenshao Sep 12, 2023
9040ea0
merge from master & revert DateTimeFormatterBuilder
wenshao Sep 12, 2023
e4c5b67
merge from master
wenshao Sep 12, 2023
8c76799
remove DIGITS_K
wenshao Sep 13, 2023
8471814
restore ZoneOffset::buildId, reduce changes
wenshao Sep 13, 2023
fba3979
restore ZoneOffset::buildId, reduce changes
wenshao Sep 13, 2023
d3ad490
restore ZoneOffset::buildId, reduce changes
wenshao Sep 13, 2023
b7a3528
simplify LocalDate::getChars
wenshao Sep 13, 2023
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
30 changes: 29 additions & 1 deletion src/java.base/share/classes/java/time/Instant.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
Expand All @@ -96,6 +98,9 @@
import java.time.temporal.ValueRange;
import java.util.Objects;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;

/**
* An instantaneous point on the time-line.
* <p>
Expand Down Expand Up @@ -210,6 +215,8 @@
public final class Instant
implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable {

private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();

/**
* Constant for the 1970-01-01T00:00:00Z epoch instant.
*/
Expand Down Expand Up @@ -1352,7 +1359,28 @@ public int hashCode() {
*/
@Override
public String toString() {
return DateTimeFormatter.ISO_INSTANT.format(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered potentially more generalizable optimizations to DateTimeFormatter.ISO_INSTANT.format(this) here?

Hand-rolling a fixed-length buffer, skipping the StringBuilder .. understandably this can have a performance edge, but perhaps a DateTimeFormatter like ISO_INSTANT can be optimized to get closer to whatever speed-up this gets you - with broader implications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance of optimizing DateTimeFormatter cannot be as fast as using ixed-length buffer directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, the optimization of DateTimeFormatter is more general, and we can spend time doing it later. The format of toString is fixed, we can not use DateTimeFormatter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered potentially more generalizable optimizations to DateTimeFormatter.ISO_INSTANT.format(this) here?

Hand-rolling a fixed-length buffer, skipping the StringBuilder .. understandably this can have a performance edge, but perhaps a DateTimeFormatter like ISO_INSTANT can be optimized to get closer to whatever speed-up this gets you - with broader implications.

I submitted an optimization for the commonly used format of DateTimeFormatter::format

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a link to that PR? Is there an RFE filed for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimization of DateTimeFormatter::format should be another PR, I created a branche but the work is unfinished.

LocalDate date = LocalDate.ofEpochDay(
Math.floorDiv(seconds, SECONDS_PER_DAY));
LocalTime time = LocalTime.ofSecondOfDay(
Math.floorMod(seconds, SECONDS_PER_DAY));

int yearSize = LocalDate.yearSize(date.getYear());
int nanoSize = LocalTime.nanoSize(nanos);

byte[] buf = new byte[yearSize + 16 + nanoSize];

int off = date.getChars(buf, 0);
buf[off] = 'T';

off = time.getChars(buf, off + 1);
LocalTime.getNanoChars(buf, off, nanos);
buf[off + nanoSize] = 'Z';

try {
return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException cce) {
throw new AssertionError(cce);
}
}

// -----------------------------------------------------------------------
Expand Down
77 changes: 59 additions & 18 deletions src/java.base/share/classes/java/time/LocalDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.IsoEra;
import java.time.chrono.IsoChronology;
Expand All @@ -103,6 +105,11 @@
import java.util.stream.LongStream;
import java.util.stream.Stream;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.ByteArrayLittleEndian;
import jdk.internal.util.DecimalDigits;

/**
* A date without a time-zone in the ISO-8601 calendar system,
* such as {@code 2007-12-03}.
Expand Down Expand Up @@ -140,6 +147,8 @@
public final class LocalDate
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable {

private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();

/**
* The minimum supported {@code LocalDate}, '-999999999-01-01'.
* This could be used by an application as a "far past" date.
Expand Down Expand Up @@ -2147,28 +2156,60 @@ public int hashCode() {
*/
@Override
public String toString() {
int yearValue = year;
int monthValue = month;
int dayValue = day;
int absYear = Math.abs(yearValue);
StringBuilder buf = new StringBuilder(10);
if (absYear < 1000) {
if (yearValue < 0) {
buf.append(yearValue - 10000).deleteCharAt(1);
} else {
buf.append(yearValue + 10000).deleteCharAt(0);
byte[] buf = new byte[yearSize(year) + 6];
getChars(buf, 0);

try {
return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException cce) {
throw new AssertionError(cce);
}
}

static int yearSize(int year) {
if (Math.abs(year) < 1000) {
return year < 0 ? 5 : 4;
}
return DecimalDigits.stringSize(year) + (year > 9999 ? 1 : 0);
}

int getChars(byte[] buf, int off) {
int year = this.year;
int yearSize = yearSize(year);
int yearAbs = Math.abs(year);

int yearEnd = off + yearSize;
if (yearAbs < 1000) {
if (year < 0) {
buf[off++] = '-';
}
int y01 = yearAbs / 100;
int y23 = yearAbs - y01 * 100;

ByteArrayLittleEndian.setInt(
buf,
off,
(DecimalDigits.digitPair(y23) << 16) | DecimalDigits.digitPair(y01));
} else {
if (yearValue > 9999) {
buf.append('+');
if (year > 9999) {
buf[off] = '+';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buf[off++] = '+';?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this place doesn't need off++

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, though it's a bit opaque that yearSize includes room for the '+' that gets added on years > 9999 but that jla.getChars won't print that. This makes the logic somewhat fragile, which I think could be improved.

}
buf.append(yearValue);
DecimalDigits.getCharsLatin1(year, yearEnd, buf);
}
return buf.append(monthValue < 10 ? "-0" : "-")
.append(monthValue)
.append(dayValue < 10 ? "-0" : "-")
.append(dayValue)
.toString();

off = yearEnd;
buf[off] = '-';
ByteArrayLittleEndian.setShort(
buf,
off + 1,
DecimalDigits.digitPair(month)); // mm
buf[off + 3] = '-';
ByteArrayLittleEndian.setShort(
buf,
off + 4,
DecimalDigits.digitPair(day)); // dd

return off + 6;
}

//-----------------------------------------------------------------------
Expand Down
22 changes: 21 additions & 1 deletion src/java.base/share/classes/java/time/LocalDateTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
Expand All @@ -96,6 +98,8 @@
import java.time.zone.ZoneRules;
import java.util.Objects;

import jdk.internal.access.SharedSecrets;

/**
* A date-time without a time-zone in the ISO-8601 calendar system,
* such as {@code 2007-12-03T10:15:30}.
Expand Down Expand Up @@ -1965,7 +1969,23 @@ public int hashCode() {
*/
@Override
public String toString() {
return date.toString() + 'T' + time.toString();
int yearSize = LocalDate.yearSize(date.getYear());
int nano = time.getNano();
int nanoSize = LocalTime.nanoSize(nano);

byte[] buf = new byte[yearSize + 15 + nanoSize];

int off = date.getChars(buf, 0);
buf[off] = 'T';
off = time.getChars(buf, off + 1);
LocalTime.getNanoChars(buf, off, nano);

try {
return SharedSecrets.getJavaLangAccess()
.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException cce) {
throw new AssertionError(cce);
}
}

//-----------------------------------------------------------------------
Expand Down
120 changes: 101 additions & 19 deletions src/java.base/share/classes/java/time/LocalTime.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
Expand All @@ -92,6 +94,11 @@
import java.time.temporal.ValueRange;
import java.util.Objects;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.ByteArrayLittleEndian;
import jdk.internal.util.DecimalDigits;

/**
* A time without a time-zone in the ISO-8601 calendar system,
* such as {@code 10:15:30}.
Expand Down Expand Up @@ -126,6 +133,8 @@
public final class LocalTime
implements Temporal, TemporalAdjuster, Comparable<LocalTime>, Serializable {

private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();

/**
* The minimum supported {@code LocalTime}, '00:00'.
* This is the time of midnight at the start of the day.
Expand Down Expand Up @@ -1629,27 +1638,100 @@ public int hashCode() {
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(18);
int hourValue = hour;
int minuteValue = minute;
int secondValue = second;
int nanoValue = nano;
buf.append(hourValue < 10 ? "0" : "").append(hourValue)
.append(minuteValue < 10 ? ":0" : ":").append(minuteValue);
if (secondValue > 0 || nanoValue > 0) {
buf.append(secondValue < 10 ? ":0" : ":").append(secondValue);
if (nanoValue > 0) {
buf.append('.');
if (nanoValue % 1000_000 == 0) {
buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1));
} else if (nanoValue % 1000 == 0) {
buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1));
} else {
buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1));
}
int nano = this.nano;
int nanoSize = LocalTime.nanoSize(nano);

byte[] buf = new byte[8 + nanoSize];
getChars(buf, 0);
LocalTime.getNanoChars(buf, 8, nano);

try {
return jla.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
} catch (CharacterCodingException cce) {
throw new AssertionError(cce);
}
}

static int nanoSize(int nano) {
if (nano == 0) {
return 0;
}

int div = nano / 1000;
int div2 = div / 1000;

if (nano - div * 1000 != 0) {
return 10;
}

return (div - div2 * 1000 == 0) ? 4 : 7;
}

int getChars(byte[] buf, int off) {
ByteArrayLittleEndian.setShort(
buf,
off,
DecimalDigits.digitPair(hour)); // hh
buf[off + 2] = ':';
ByteArrayLittleEndian.setShort(
buf,
off + 3,
DecimalDigits.digitPair(minute)); // minute
buf[off + 5] = ':';
ByteArrayLittleEndian.setShort(
buf,
off + 6,
DecimalDigits.digitPair(second)); // second
return off + 8;
}

static void getNanoChars(byte[] buf, int off, int nano) {
if (nano == 0) {
return;
}

int div = nano / 1000;
int div2 = div / 1000;

int div2_k = div2 / 100;
buf[off] = '.';
buf[off + 1] = (byte) ('0' + div2_k);
ByteArrayLittleEndian.setShort(
buf,
off + 2,
DecimalDigits.digitPair(div2 - div2_k * 100)
);
off += 4;

int rem1 = nano - div * 1000;
int rem2;
if (rem1 == 0) {
rem2 = div - div2 * 1000;
if (rem2 == 0) {
return;
}
} else {
rem2 = div - div2 * 1000;
}

int rem2_k = rem2 / 100;
buf[off] = (byte) ('0' + rem2_k);
ByteArrayLittleEndian.setShort(
buf,
off + 1,
DecimalDigits.digitPair(rem2 - rem2_k * 100)
);
off += 3;

if (rem1 != 0) {
int rem1_k = rem1 / 100;
buf[off] = (byte) ('0' + rem1_k);
ByteArrayLittleEndian.setShort(
buf,
off + 1,
DecimalDigits.digitPair(rem1 - rem1_k * 100)
);
}
return buf.toString();
}

//-----------------------------------------------------------------------
Expand Down
Loading