Skip to content

Commit b9824e1

Browse files
author
tobo
committed
[util] speedup GmTimeR outside LUT interval
commit_hash:d57be220df393c193619ef5ed129ec4436540629
1 parent 8fe9394 commit b9824e1

File tree

3 files changed

+133
-81
lines changed

3 files changed

+133
-81
lines changed

util/datetime/base_ut.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,8 @@ Y_UNIT_TEST_SUITE(TDateTimeTest) {
310310
&& true;
311311
}
312312

313-
Y_UNIT_TEST(TestGmTimeR) {
314-
time_t starttime = static_cast<time_t>(Max<i64>(-12244089600LL, Min<time_t>())); // 1-Jan-1582
315-
time_t finishtime = static_cast<time_t>(Min<i64>(0xFFFFFFFF * 20, Max<time_t>()));
316-
time_t step = (finishtime - starttime) / 25;
313+
void TestGmTimeR(time_t starttime, time_t finishtime, int steps) {
314+
time_t step = (finishtime - starttime) / steps;
317315
struct tm tms0, tms1;
318316
struct tm* ptm0 = nullptr;
319317
struct tm* ptm1 = nullptr;
@@ -336,6 +334,18 @@ Y_UNIT_TEST_SUITE(TDateTimeTest) {
336334
UNIT_ASSERT(CompareTMFull(ptm0, ptm1));
337335
}
338336
}
337+
338+
Y_UNIT_TEST(TestGmTimeRLongRange) {
339+
time_t starttime = static_cast<time_t>(-86397839500LL); // 29-Jan-2668 B.C.
340+
time_t finishtime = static_cast<time_t>(0xFFFFFFFF * 20);
341+
TestGmTimeR(starttime, finishtime, 101);
342+
}
343+
344+
Y_UNIT_TEST(TestGmTimeRNowdays) {
345+
time_t starttime = static_cast<time_t>(0); // 1970
346+
time_t finishtime = static_cast<time_t>(6307200000LL); // 2170
347+
TestGmTimeR(starttime, finishtime, 303);
348+
}
339349
} // Y_UNIT_TEST_SUITE(TDateTimeTest)
340350

341351
Y_UNIT_TEST_SUITE(DateTimeTest) {

util/datetime/systime.cpp

Lines changed: 110 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ char* ctime_r(const time_t* clock, char* buf) {
7575
namespace {
7676
constexpr int STRUCT_TM_BASE_YEAR = 1900;
7777
constexpr int UNIX_TIME_BASE_YEAR = 1970;
78-
constexpr long SECONDS_PER_DAY = (24L * 60L * 60L);
78+
constexpr ui64 SECONDS_PER_DAY = (24L * 60L * 60L);
7979

8080
constexpr bool IsLeapYear(int year) {
8181
if (year % 4 != 0) {
@@ -94,7 +94,29 @@ namespace {
9494
return IsLeapYear(year) ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR;
9595
}
9696

97-
constexpr ui64 FOUR_CENTURIES = (400 * 365 + 100 - 3);
97+
constexpr ui32 FOUR_CENTURY_YEARS = 400;
98+
99+
constexpr ui32 LeapYearCount(ui32 years) {
100+
return years / 4 - years / 100 + years / 400;
101+
}
102+
103+
constexpr ui32 FOUR_CENTURY_DAYS = FOUR_CENTURY_YEARS * DAYS_IN_YEAR + LeapYearCount(FOUR_CENTURY_YEARS);
104+
105+
constexpr int FindYearWithin4Centuries(ui32& dayno) {
106+
Y_ASSERT(dayno < FOUR_CENTURY_DAYS);
107+
ui32 years = dayno / DAYS_IN_YEAR;
108+
109+
const ui32 diff = years * DAYS_IN_YEAR + LeapYearCount(years);
110+
111+
if (diff <= dayno) {
112+
dayno -= diff;
113+
} else {
114+
dayno -= diff - YearSize(static_cast<int>(years));
115+
--years;
116+
}
117+
118+
return static_cast<int>(years);
119+
}
98120

99121
constexpr ui16 MONTH_TO_DAYS[12] = {
100122
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
@@ -103,73 +125,73 @@ namespace {
103125
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
104126

105127
struct TMonth32LUT {
106-
char LastMonthDay32[12];
107-
char FirstMonthDay32[12];
128+
ui8 LastMonthDay32[12];
129+
ui8 FirstMonthDay32[12];
108130
};
109131

110-
constexpr TMonth32LUT MONTH_32_LUT[2] = {
111-
{
112-
// common year
113-
.LastMonthDay32 = {31, 27, 26, 24, 23, 21, 20, 19, 17, 16, 14, 13},
114-
.FirstMonthDay32 = {0, 1, 5, 6, 8, 9, 11, 12, 13, 15, 16, 18},
115-
},
116-
{
117-
// leap year
118-
.LastMonthDay32 = {31, 28, 27, 25, 24, 22, 21, 20, 18, 17, 15, 14},
119-
.FirstMonthDay32 = {0, 1, 4, 5, 7, 8, 10, 11, 12, 14, 15, 17},
120-
},
132+
constexpr TMonth32LUT COMMON_YEAR = {
133+
.LastMonthDay32 = {31, 27, 26, 24, 23, 21, 20, 19, 17, 16, 14, 13},
134+
.FirstMonthDay32 = {0, 1, 5, 6, 8, 9, 11, 12, 13, 15, 16, 18},
121135
};
122136

123-
constexpr int DayOfYearToMonth(ui64& yearDay, bool leapYear) {
124-
Y_ASSERT(yearDay < 366);
125-
int approxMonth = yearDay / 32;
126-
int approxMDay = yearDay % 32;
127-
int dayThreshold = MONTH_32_LUT[leapYear].LastMonthDay32[approxMonth];
128-
int currentMonthMDayOffset = MONTH_32_LUT[leapYear].FirstMonthDay32[approxMonth];
129-
bool nextMonth = (approxMDay >= dayThreshold);
130-
int dayCorrection = nextMonth ? -dayThreshold : currentMonthMDayOffset;
131-
int day = approxMDay + dayCorrection;
132-
int month = approxMonth + nextMonth;
133-
yearDay = day;
137+
constexpr int DayOfYearToMonth(ui32& yearDay, const bool leapYear) {
138+
Y_ASSERT(yearDay < DAYS_IN_YEAR + leapYear);
139+
if (leapYear) {
140+
if (yearDay > 59) {
141+
--yearDay;
142+
} else if (yearDay == 59) {
143+
// February, 29th
144+
yearDay = 28;
145+
return 1;
146+
}
147+
}
148+
const int approxMonth = static_cast<int>(yearDay / 32);
149+
const int approxMDay = static_cast<int>(yearDay % 32);
150+
const int dayThreshold = COMMON_YEAR.LastMonthDay32[approxMonth];
151+
const int currentMonthMDayOffset = COMMON_YEAR.FirstMonthDay32[approxMonth];
152+
const bool nextMonth = (approxMDay >= dayThreshold);
153+
const int dayCorrection = nextMonth ? -dayThreshold : currentMonthMDayOffset;
154+
yearDay = approxMDay + dayCorrection;
155+
const int month = approxMonth + nextMonth;
134156
return month;
135157
}
136158

137159
class TDayNoToYearLookupTable {
138160
static constexpr int TableSize = 128;
139-
// lookup table for years in [1970, 1970 + 128 = 2098] range
161+
// lookup table for years in [StartYear, StartYear + TableSize] range
140162
ui16 DaysSinceEpoch[TableSize] = {};
141163

142164
public:
165+
static constexpr int StartYear = 1970;
166+
static constexpr int StartDays = (StartYear - UNIX_TIME_BASE_YEAR) * DAYS_IN_YEAR + LeapYearCount(StartYear - 1) - LeapYearCount(UNIX_TIME_BASE_YEAR - 1);
167+
static constexpr i64 MinTimestamp = StartDays * static_cast<i64>(SECONDS_PER_DAY);
168+
static constexpr i64 MaxTimestamp = MinTimestamp + static_cast<i64>(TableSize) * DAYS_IN_LEAP_YEAR * SECONDS_PER_DAY - 1;
143169
constexpr TDayNoToYearLookupTable() {
144170
ui16 daysAccumulated = 0;
145171

146-
for (int year = UNIX_TIME_BASE_YEAR; year < UNIX_TIME_BASE_YEAR + TableSize; ++year) {
172+
for (int year = StartYear; year < StartYear + TableSize; ++year) {
147173
daysAccumulated += YearSize(year);
148-
DaysSinceEpoch[year - UNIX_TIME_BASE_YEAR] = daysAccumulated;
174+
DaysSinceEpoch[year - StartYear] = daysAccumulated;
149175
}
150176
}
151177

152178
// lookup year by days since epoch, decrement day counter to the corresponding amount of days.
153179
// The method returns the last year in the table, if year is too big
154-
int FindYear(ui64& days) const {
155-
if (days >= DaysSinceEpoch[TableSize - 1]) {
156-
days -= DaysSinceEpoch[TableSize - 1];
157-
return TableSize + UNIX_TIME_BASE_YEAR;
158-
}
159-
const ui64 yearIndex = days / DAYS_IN_LEAP_YEAR;
180+
int FindYear(ui32& days) const {
181+
const ui32 yearIndex = days / DAYS_IN_LEAP_YEAR;
160182

161183
// we can miss by at most 1 year
162184
Y_ASSERT(yearIndex < TableSize);
163185
if (const auto diff = DaysSinceEpoch[yearIndex]; diff <= days) {
164186
days -= diff;
165-
return static_cast<int>(yearIndex + UNIX_TIME_BASE_YEAR + 1);
187+
return static_cast<int>(yearIndex + StartYear + 1);
166188
}
167189

168190
if (yearIndex > 0) {
169191
days -= DaysSinceEpoch[yearIndex - 1];
170192
}
171193

172-
return static_cast<int>(yearIndex + UNIX_TIME_BASE_YEAR);
194+
return static_cast<int>(yearIndex + StartYear);
173195
}
174196
};
175197

@@ -209,55 +231,72 @@ time_t TimeGM(const struct tm* t) {
209231

210232
struct tm* GmTimeR(const time_t* timer, struct tm* tmbuf) {
211233
i64 time = static_cast<i64>(*timer);
234+
tm* resut = tmbuf;
235+
int dayClock;
236+
ui32 daysRemaining;
237+
bool isLeapYear;
238+
239+
if (time >= TDayNoToYearLookupTable::MinTimestamp && time <= TDayNoToYearLookupTable::MaxTimestamp)
240+
{
241+
dayClock = static_cast<int>(time % SECONDS_PER_DAY);
242+
daysRemaining = time / SECONDS_PER_DAY;
243+
tmbuf->tm_wday = static_cast<int>((daysRemaining + 4) % 7); // Day 0 was a thursday
244+
daysRemaining -= TDayNoToYearLookupTable::StartDays;
245+
const int year = DAYS_TO_YEAR_LOOKUP.FindYear(daysRemaining);
246+
isLeapYear = IsLeapYear(year);
247+
tmbuf->tm_year = year - STRUCT_TM_BASE_YEAR;
248+
} else {
249+
i64 year = UNIX_TIME_BASE_YEAR;
212250

213-
ui64 dayclock, dayno;
214-
int year = UNIX_TIME_BASE_YEAR;
215-
216-
if (Y_UNLIKELY(time < 0)) {
217-
ui64 shift = (ui64)(-time - 1) / (FOUR_CENTURIES * SECONDS_PER_DAY) + 1;
218-
time += shift * (FOUR_CENTURIES * SECONDS_PER_DAY);
219-
year -= shift * 400;
220-
}
251+
if (Y_UNLIKELY(time < 0)) {
252+
const ui64 shift = (ui64)(-time - 1) / (static_cast<ui64>(FOUR_CENTURY_DAYS) * SECONDS_PER_DAY) + 1;
253+
time += static_cast<i64>(shift * FOUR_CENTURY_DAYS * SECONDS_PER_DAY);
254+
year -= static_cast<i64>(shift * FOUR_CENTURY_YEARS);
255+
}
221256

222-
dayclock = (ui64)time % SECONDS_PER_DAY;
223-
dayno = (ui64)time / SECONDS_PER_DAY;
257+
dayClock = static_cast<int>(time % SECONDS_PER_DAY);
258+
ui64 dayNo = (ui64)time / SECONDS_PER_DAY;
259+
tmbuf->tm_wday = (dayNo + 4) % 7; // Day 0 was a thursday
224260

225-
if (Y_UNLIKELY(dayno >= FOUR_CENTURIES)) {
226-
year += 400 * (dayno / FOUR_CENTURIES);
227-
dayno = dayno % FOUR_CENTURIES;
228-
}
261+
if (int shiftYears = (year - 1) % FOUR_CENTURY_YEARS; shiftYears != 0) {
262+
if (shiftYears < 0) {
263+
shiftYears += FOUR_CENTURY_YEARS;
264+
}
265+
year -= shiftYears;
266+
dayNo += shiftYears * DAYS_IN_YEAR + LeapYearCount(shiftYears);
267+
}
229268

230-
tmbuf->tm_sec = dayclock % 60;
231-
tmbuf->tm_min = (dayclock % 3600) / 60;
232-
tmbuf->tm_hour = dayclock / 3600;
233-
tmbuf->tm_wday = (dayno + 4) % 7; // Day 0 was a thursday
269+
if (Y_UNLIKELY(dayNo >= FOUR_CENTURY_DAYS)) {
270+
year += FOUR_CENTURY_YEARS * (dayNo / FOUR_CENTURY_DAYS);
271+
dayNo = dayNo % FOUR_CENTURY_DAYS;
272+
}
234273

235-
if (Y_LIKELY(year == UNIX_TIME_BASE_YEAR)) {
236-
year = DAYS_TO_YEAR_LOOKUP.FindYear(dayno);
237-
}
274+
daysRemaining = dayNo;
275+
const int yearDiff = FindYearWithin4Centuries(daysRemaining);
276+
year += yearDiff;
277+
isLeapYear = IsLeapYear(yearDiff + 1);
278+
tmbuf->tm_year = static_cast<int>(year - STRUCT_TM_BASE_YEAR);
238279

239-
bool isLeapYear = IsLeapYear(year);
240-
for (;;) {
241-
const ui16 yearSize = isLeapYear ? DAYS_IN_LEAP_YEAR : DAYS_IN_YEAR;
242-
if (dayno < yearSize) {
243-
break;
280+
// check year overflow
281+
if (Y_UNLIKELY(year - STRUCT_TM_BASE_YEAR != tmbuf->tm_year)) {
282+
resut = nullptr;
244283
}
245-
dayno -= yearSize;
246-
++year;
247-
isLeapYear = IsLeapYear(year);
248284
}
249285

250-
tmbuf->tm_year = year - STRUCT_TM_BASE_YEAR;
251-
tmbuf->tm_yday = dayno;
252-
tmbuf->tm_mon = DayOfYearToMonth(dayno, isLeapYear);
253-
tmbuf->tm_mday = dayno + 1;
286+
tmbuf->tm_sec = dayClock % 60;
287+
tmbuf->tm_min = (dayClock % 3600) / 60;
288+
tmbuf->tm_hour = dayClock / 3600;
289+
290+
tmbuf->tm_yday = static_cast<int>(daysRemaining);
291+
tmbuf->tm_mon = DayOfYearToMonth(daysRemaining, isLeapYear);
292+
tmbuf->tm_mday = static_cast<int>(daysRemaining + 1);
254293
tmbuf->tm_isdst = 0;
255294
#ifndef _win_
256295
tmbuf->tm_gmtoff = 0;
257296
tmbuf->tm_zone = (char*)"UTC";
258297
#endif
259298

260-
return tmbuf;
299+
return resut;
261300
}
262301

263302
TString CTimeR(const time_t* timer) {

util/datetime/systime.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,29 @@
55

66
#include <ctime>
77

8-
// timegm and gmtime_r versions that don't need access to filesystem or a big stack
8+
// timegm and gmtime_r versions that don't need access to the file system or a large stack
99
time_t TimeGM(const struct tm* t);
10+
11+
// the same as gmtime_r, but 3–5 times faster
1012
struct tm* GmTimeR(const time_t* timer, struct tm* tmbuf);
11-
// safe version of ctime, convinient version of ctime_r
13+
14+
// safe version of ctime, convenient version of ctime_r
1215
TString CTimeR(const time_t* timer);
1316

1417
#ifdef _win_
1518
#include <util/system/winint.h>
1619
#include <winsock2.h>
1720

18-
// Convert FILETIME to timeval - seconds and microseconds.
21+
// Convert FILETIME to timeval: seconds and microseconds.
1922
void FileTimeToTimeval(const FILETIME* ft, struct timeval* tv);
2023

21-
// Convert FILETIME to timespec - seconds and nanoseconds.
24+
// Convert FILETIME to timespec: seconds and nanoseconds.
2225
void FileTimeToTimespec(const FILETIME& ft, struct timespec* ts);
2326

24-
// obtains the current time, expressed as seconds and microseconds since 00:00 UTC, January 1, 1970
27+
// Obtain the current time, expressed as seconds and microseconds since 00:00 UTC, January 1, 1970
2528
int gettimeofday(struct timeval* tp, void*);
2629

27-
// thou should not mix these with non-_r functions
30+
// You should not mix these with non-_r functions
2831
tm* localtime_r(const time_t* clock, tm* result);
2932
tm* gmtime_r(const time_t* clock, tm* result);
3033
char* ctime_r(const time_t* clock, char* buf);

0 commit comments

Comments
 (0)