Skip to content

Commit

Permalink
Optimize DateTime decomposition in CoreLib (dotnet#1944)
Browse files Browse the repository at this point in the history
* Optimize DateTime decomposition in CoreLib
Add uses of DateTime.GetDate (renamed from GetDatePart) where appropriate
Add DateTime.GetTime method to eliminate duplicate computations

* Remove unused arithmetic from DateTime.GetTime
  • Loading branch information
ts2do authored and danmoseley committed Jan 21, 2020
1 parent 26298bc commit e5444a1
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,47 +41,49 @@ private static bool TryFormatDateTimeG(DateTime value, TimeSpan offset, Span<byt
// Hoist most of the bounds checks on buffer.
{ var unused = destination[MinimumBytesNeeded - 1]; }

// TODO: Introduce an API which can parse DateTime instances efficiently, pulling out
// all their properties (Month, Day, etc.) in one shot. This would help avoid the
// duplicate work that implicitly results from calling these properties individually.
value.GetDate(out int year, out int month, out int day);
value.GetTime(out int hour, out int minute, out int second);

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Month, destination, 0);
FormattingHelpers.WriteTwoDecimalDigits((uint)month, destination, 0);
destination[2] = Utf8Constants.Slash;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 3);
FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 3);
destination[5] = Utf8Constants.Slash;

FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 6);
FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 6);
destination[10] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 11);
FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 11);
destination[13] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 14);
FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 14);
destination[16] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 17);
FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 17);

if (offset != Utf8Constants.NullUtcOffset)
{
int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
byte sign;

if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
if (offsetTotalMinutes < 0)
{
sign = Utf8Constants.Minus;
offset = TimeSpan.FromTicks(-offset.Ticks);
offsetTotalMinutes = -offsetTotalMinutes;
}
else
{
sign = Utf8Constants.Plus;
}

int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);

// Writing the value backward allows the JIT to optimize by
// performing a single bounds check against buffer.

FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Minutes, destination, 24);
FormattingHelpers.WriteTwoDecimalDigits((uint)offsetMinutes, destination, 24);
destination[23] = Utf8Constants.Colon;
FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Hours, destination, 21);
FormattingHelpers.WriteTwoDecimalDigits((uint)offsetHours, destination, 21);
destination[20] = sign;
destination[19] = Utf8Constants.Space;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ private static bool TryFormatDateTimeL(DateTime value, Span<byte> destination, o
return false;
}

value.GetDate(out int year, out int month, out int day);
value.GetTime(out int hour, out int minute, out int second);

uint dayAbbrev = s_dayAbbreviationsLowercase[(int)value.DayOfWeek];

destination[0] = (byte)dayAbbrev;
Expand All @@ -32,27 +35,27 @@ private static bool TryFormatDateTimeL(DateTime value, Span<byte> destination, o
destination[3] = Utf8Constants.Comma;
destination[4] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 5);
FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 5);
destination[7] = Utf8Constants.Space;

uint monthAbbrev = s_monthAbbreviationsLowercase[value.Month - 1];
uint monthAbbrev = s_monthAbbreviationsLowercase[month - 1];
destination[8] = (byte)monthAbbrev;
monthAbbrev >>= 8;
destination[9] = (byte)monthAbbrev;
monthAbbrev >>= 8;
destination[10] = (byte)monthAbbrev;
destination[11] = Utf8Constants.Space;

FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 12);
FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 12);
destination[16] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 17);
FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 17);
destination[19] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 20);
FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 20);
destination[22] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 23);
FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 23);
destination[25] = Utf8Constants.Space;

destination[26] = GMT1Lowercase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,46 +51,52 @@ private static bool TryFormatDateTimeO(DateTime value, TimeSpan offset, Span<byt
// Hoist most of the bounds checks on buffer.
{ _ = destination[MinimumBytesNeeded - 1]; }

FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 0);
value.GetDate(out int year, out int month, out int day);
value.GetTimePrecise(out int hour, out int minute, out int second, out int ticks);

FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 0);
destination[4] = Utf8Constants.Minus;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Month, destination, 5);
FormattingHelpers.WriteTwoDecimalDigits((uint)month, destination, 5);
destination[7] = Utf8Constants.Minus;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 8);
FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 8);
destination[10] = TimeMarker;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 11);
FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 11);
destination[13] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 14);
FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 14);
destination[16] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 17);
FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 17);
destination[19] = Utf8Constants.Period;

FormattingHelpers.WriteDigits((uint)((ulong)value.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
FormattingHelpers.WriteDigits((uint)ticks, destination.Slice(20, 7));

if (kind == DateTimeKind.Local)
{
int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
byte sign;

if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
if (offsetTotalMinutes < 0)
{
sign = Utf8Constants.Minus;
offset = TimeSpan.FromTicks(-offset.Ticks);
offsetTotalMinutes = -offsetTotalMinutes;
}
else
{
sign = Utf8Constants.Plus;
}

int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);

// Writing the value backward allows the JIT to optimize by
// performing a single bounds check against buffer.

FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
FormattingHelpers.WriteTwoDecimalDigits((uint)offsetMinutes, destination, 31);
destination[30] = Utf8Constants.Colon;
FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
FormattingHelpers.WriteTwoDecimalDigits((uint)offsetHours, destination, 28);
destination[27] = sign;
}
else if (kind == DateTimeKind.Utc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ private static bool TryFormatDateTimeR(DateTime value, Span<byte> destination, o
return false;
}

value.GetDate(out int year, out int month, out int day);
value.GetTime(out int hour, out int minute, out int second);

uint dayAbbrev = s_dayAbbreviations[(int)value.DayOfWeek];

destination[0] = (byte)dayAbbrev;
Expand All @@ -32,27 +35,27 @@ private static bool TryFormatDateTimeR(DateTime value, Span<byte> destination, o
destination[3] = Utf8Constants.Comma;
destination[4] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 5);
FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 5);
destination[7] = Utf8Constants.Space;

uint monthAbbrev = s_monthAbbreviations[value.Month - 1];
uint monthAbbrev = s_monthAbbreviations[month - 1];
destination[8] = (byte)monthAbbrev;
monthAbbrev >>= 8;
destination[9] = (byte)monthAbbrev;
monthAbbrev >>= 8;
destination[10] = (byte)monthAbbrev;
destination[11] = Utf8Constants.Space;

FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 12);
FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 12);
destination[16] = Utf8Constants.Space;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 17);
FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 17);
destination[19] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 20);
FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 20);
destination[22] = Utf8Constants.Colon;

FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 23);
FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 23);
destination[25] = Utf8Constants.Space;

destination[26] = GMT1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,17 @@ internal FullSystemTime(long ticks)
{
DateTime dt = new DateTime(ticks);

int year, month, day;
dt.GetDatePart(out year, out month, out day);
dt.GetDate(out int year, out int month, out int day);
dt.GetTime(out int hour, out int minute, out int second, out int millisecond);

systemTime.Year = (ushort)year;
systemTime.Month = (ushort)month;
systemTime.DayOfWeek = (ushort)dt.DayOfWeek;
systemTime.Day = (ushort)day;
systemTime.Hour = (ushort)dt.Hour;
systemTime.Minute = (ushort)dt.Minute;
systemTime.Second = (ushort)dt.Second;
systemTime.Milliseconds = (ushort)dt.Millisecond;
systemTime.Hour = (ushort)hour;
systemTime.Minute = (ushort)minute;
systemTime.Second = (ushort)second;
systemTime.Milliseconds = (ushort)millisecond;
hundredNanoSecond = 0;
}
}
Expand Down
44 changes: 40 additions & 4 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ public DateTime AddMinutes(double value)
public DateTime AddMonths(int months)
{
if (months < -120000 || months > 120000) throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateTimeBadMonths);
GetDatePart(out int y, out int m, out int d);
GetDate(out int y, out int m, out int d);
int i = m - 1 + months;
if (i >= 0)
{
Expand Down Expand Up @@ -935,10 +935,10 @@ private int GetDatePart(int part)
return n - days[m - 1] + 1;
}

// Exactly the same as GetDatePart(int part), except computing all of
// year/month/day rather than just one of them. Used when all three
// Exactly the same as GetDatePart, except computing all of
// year/month/day rather than just one of them. Used when all three
// are needed rather than redoing the computations for each.
internal void GetDatePart(out int year, out int month, out int day)
internal void GetDate(out int year, out int month, out int day)
{
long ticks = InternalTicks;
// n = number of days since 1/1/0001
Expand Down Expand Up @@ -980,6 +980,42 @@ internal void GetDatePart(out int year, out int month, out int day)
day = n - days[m - 1] + 1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void GetTime(out int hour, out int minute, out int second)
{
long n = InternalTicks / TicksPerSecond;
n = Math.DivRem(n, 60, out long m);
second = (int)m;
n = Math.DivRem(n, 60, out m);
minute = (int)m;
hour = (int)(n % 24);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void GetTime(out int hour, out int minute, out int second, out int millisecond)
{
long n = InternalTicks / TicksPerMillisecond;
n = Math.DivRem(n, 1000, out long m);
millisecond = (int)m;
n = Math.DivRem(n, 60, out m);
second = (int)m;
n = Math.DivRem(n, 60, out m);
minute = (int)m;
hour = (int)(n % 24);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void GetTimePrecise(out int hour, out int minute, out int second, out int tick)
{
long n = Math.DivRem(InternalTicks, TicksPerSecond, out long m);
tick = (int)m;
n = Math.DivRem(n, 60, out m);
second = (int)m;
n = Math.DivRem(n, 60, out m);
minute = (int)m;
hour = (int)(n % 24);
}

// Returns the day-of-month part of this DateTime. The returned
// value is an integer between 1 and 31.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1154,38 +1154,45 @@ private static bool TryFormatO(DateTime dateTime, TimeSpan offset, Span<char> de
// Hoist most of the bounds checks on destination.
{ _ = destination[MinimumBytesNeeded - 1]; }

WriteFourDecimalDigits((uint)dateTime.Year, destination, 0);
dateTime.GetDate(out int year, out int month, out int day);
dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick);

WriteFourDecimalDigits((uint)year, destination, 0);
destination[4] = '-';
WriteTwoDecimalDigits((uint)dateTime.Month, destination, 5);
WriteTwoDecimalDigits((uint)month, destination, 5);
destination[7] = '-';
WriteTwoDecimalDigits((uint)dateTime.Day, destination, 8);
WriteTwoDecimalDigits((uint)day, destination, 8);
destination[10] = 'T';
WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 11);
WriteTwoDecimalDigits((uint)hour, destination, 11);
destination[13] = ':';
WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 14);
WriteTwoDecimalDigits((uint)minute, destination, 14);
destination[16] = ':';
WriteTwoDecimalDigits((uint)dateTime.Second, destination, 17);
WriteTwoDecimalDigits((uint)second, destination, 17);
destination[19] = '.';
WriteDigits((uint)((ulong)dateTime.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
WriteDigits((uint)tick, destination.Slice(20, 7));

if (kind == DateTimeKind.Local)
{
int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);

char sign;
if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
if (offsetTotalMinutes < 0)
{
sign = '-';
offset = TimeSpan.FromTicks(-offset.Ticks);
offsetTotalMinutes = -offsetTotalMinutes;
}
else
{
sign = '+';
}

int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);

// Writing the value backward allows the JIT to optimize by
// performing a single bounds check against buffer.
WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
WriteTwoDecimalDigits((uint)offsetMinutes, destination, 31);
destination[30] = ':';
WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
WriteTwoDecimalDigits((uint)offsetHours, destination, 28);
destination[27] = sign;
}
else if (kind == DateTimeKind.Utc)
Expand Down Expand Up @@ -1216,7 +1223,8 @@ private static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span<char> de
dateTime -= offset;
}

dateTime.GetDatePart(out int year, out int month, out int day);
dateTime.GetDate(out int year, out int month, out int day);
dateTime.GetTime(out int hour, out int minute, out int second);

string dayAbbrev = InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek];
Debug.Assert(dayAbbrev.Length == 3);
Expand All @@ -1237,11 +1245,11 @@ private static bool TryFormatR(DateTime dateTime, TimeSpan offset, Span<char> de
destination[11] = ' ';
WriteFourDecimalDigits((uint)year, destination, 12);
destination[16] = ' ';
WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 17);
WriteTwoDecimalDigits((uint)hour, destination, 17);
destination[19] = ':';
WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 20);
WriteTwoDecimalDigits((uint)minute, destination, 20);
destination[22] = ':';
WriteTwoDecimalDigits((uint)dateTime.Second, destination, 23);
WriteTwoDecimalDigits((uint)second, destination, 23);
destination[25] = ' ';
destination[26] = 'G';
destination[27] = 'M';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@ private bool LunarToGregorian(int lunarYear, int lunarMonth, int lunarDate, out
private DateTime LunarToTime(DateTime time, int year, int month, int day)
{
LunarToGregorian(year, month, day, out int gy, out int gm, out int gd);
return GregorianCalendar.GetDefaultInstance().ToDateTime(gy, gm, gd, time.Hour, time.Minute, time.Second, time.Millisecond);
time.GetTime(out int hour, out int minute, out int second, out int millisecond);
return GregorianCalendar.GetDefaultInstance().ToDateTime(gy, gm, gd, hour, minute, second, millisecond);
}

private void TimeToLunar(DateTime time, out int year, out int month, out int day)
Expand Down
Loading

0 comments on commit e5444a1

Please sign in to comment.