Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0a87ddf

Browse files
committed
Cache the result of converting now() to a struct pg_tm.
SQL operations such as CURRENT_DATE, CURRENT_TIME, LOCALTIME, and conversion of "now" in a datetime input string have to obtain the transaction start timestamp ("now()") as a broken-down struct pg_tm. This is a remarkably expensive conversion, and since now() does not change intra-transaction, it doesn't really need to be done more than once per transaction. Introducing a simple cache provides visible speedups in queries that compute these values many times, for example insertion of many rows that use a default value of CURRENT_DATE. Peter Smith, with a bit of kibitzing by me Discussion: https://postgr.es/m/CAHut+Pu89TWjq530V2gY5O6SWi=OEJMQ_VHMt8bdZB_9JFna5A@mail.gmail.com
1 parent e21cbb4 commit 0a87ddf

File tree

2 files changed

+80
-36
lines changed

2 files changed

+80
-36
lines changed

src/backend/utils/adt/date.c

+24-25
Original file line numberDiff line numberDiff line change
@@ -299,20 +299,31 @@ EncodeSpecialDate(DateADT dt, char *str)
299299
DateADT
300300
GetSQLCurrentDate(void)
301301
{
302-
TimestampTz ts;
303-
struct pg_tm tt,
304-
*tm = &tt;
305-
fsec_t fsec;
306-
int tz;
302+
struct pg_tm tm;
307303

308-
ts = GetCurrentTransactionStartTimestamp();
304+
static int cache_year = 0;
305+
static int cache_mon = 0;
306+
static int cache_mday = 0;
307+
static DateADT cache_date;
309308

310-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
311-
ereport(ERROR,
312-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
313-
errmsg("timestamp out of range")));
309+
GetCurrentDateTime(&tm);
314310

315-
return date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
311+
/*
312+
* date2j involves several integer divisions; moreover, unless our session
313+
* lives across local midnight, we don't really have to do it more than
314+
* once. So it seems worth having a separate cache here.
315+
*/
316+
if (tm.tm_year != cache_year ||
317+
tm.tm_mon != cache_mon ||
318+
tm.tm_mday != cache_mday)
319+
{
320+
cache_date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
321+
cache_year = tm.tm_year;
322+
cache_mon = tm.tm_mon;
323+
cache_mday = tm.tm_mday;
324+
}
325+
326+
return cache_date;
316327
}
317328

318329
/*
@@ -322,18 +333,12 @@ TimeTzADT *
322333
GetSQLCurrentTime(int32 typmod)
323334
{
324335
TimeTzADT *result;
325-
TimestampTz ts;
326336
struct pg_tm tt,
327337
*tm = &tt;
328338
fsec_t fsec;
329339
int tz;
330340

331-
ts = GetCurrentTransactionStartTimestamp();
332-
333-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
334-
ereport(ERROR,
335-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
336-
errmsg("timestamp out of range")));
341+
GetCurrentTimeUsec(tm, &fsec, &tz);
337342

338343
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
339344
tm2timetz(tm, fsec, tz, result);
@@ -348,18 +353,12 @@ TimeADT
348353
GetSQLLocalTime(int32 typmod)
349354
{
350355
TimeADT result;
351-
TimestampTz ts;
352356
struct pg_tm tt,
353357
*tm = &tt;
354358
fsec_t fsec;
355359
int tz;
356360

357-
ts = GetCurrentTransactionStartTimestamp();
358-
359-
if (timestamp2tm(ts, &tz, tm, &fsec, NULL, NULL) != 0)
360-
ereport(ERROR,
361-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
362-
errmsg("timestamp out of range")));
361+
GetCurrentTimeUsec(tm, &fsec, &tz);
363362

364363
tm2time(tm, fsec, &result);
365364
AdjustTimeForTypmod(&result, typmod);

src/backend/utils/adt/datetime.c

+56-11
Original file line numberDiff line numberDiff line change
@@ -339,35 +339,80 @@ j2day(int date)
339339
/*
340340
* GetCurrentDateTime()
341341
*
342-
* Get the transaction start time ("now()") broken down as a struct pg_tm.
342+
* Get the transaction start time ("now()") broken down as a struct pg_tm,
343+
* converted according to the session timezone setting.
344+
*
345+
* This is just a convenience wrapper for GetCurrentTimeUsec, to cover the
346+
* case where caller doesn't need either fractional seconds or tz offset.
343347
*/
344348
void
345349
GetCurrentDateTime(struct pg_tm *tm)
346350
{
347-
int tz;
348351
fsec_t fsec;
349352

350-
timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, &fsec,
351-
NULL, NULL);
352-
/* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
353+
GetCurrentTimeUsec(tm, &fsec, NULL);
353354
}
354355

355356
/*
356357
* GetCurrentTimeUsec()
357358
*
358359
* Get the transaction start time ("now()") broken down as a struct pg_tm,
359-
* including fractional seconds and timezone offset.
360+
* including fractional seconds and timezone offset. The time is converted
361+
* according to the session timezone setting.
362+
*
363+
* Callers may pass tzp = NULL if they don't need the offset, but this does
364+
* not affect the conversion behavior (unlike timestamp2tm()).
365+
*
366+
* Internally, we cache the result, since this could be called many times
367+
* in a transaction, within which now() doesn't change.
360368
*/
361369
void
362370
GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
363371
{
364-
int tz;
372+
TimestampTz cur_ts = GetCurrentTransactionStartTimestamp();
373+
374+
/*
375+
* The cache key must include both current time and current timezone. By
376+
* representing the timezone by just a pointer, we're assuming that
377+
* distinct timezone settings could never have the same pointer value.
378+
* This is true by virtue of the hashtable used inside pg_tzset();
379+
* however, it might need another look if we ever allow entries in that
380+
* hash to be recycled.
381+
*/
382+
static TimestampTz cache_ts = 0;
383+
static pg_tz *cache_timezone = NULL;
384+
static struct pg_tm cache_tm;
385+
static fsec_t cache_fsec;
386+
static int cache_tz;
387+
388+
if (cur_ts != cache_ts || session_timezone != cache_timezone)
389+
{
390+
/*
391+
* Make sure cache is marked invalid in case of error after partial
392+
* update within timestamp2tm.
393+
*/
394+
cache_timezone = NULL;
395+
396+
/*
397+
* Perform the computation, storing results into cache. We do not
398+
* really expect any error here, since current time surely ought to be
399+
* within range, but check just for sanity's sake.
400+
*/
401+
if (timestamp2tm(cur_ts, &cache_tz, &cache_tm, &cache_fsec,
402+
NULL, session_timezone) != 0)
403+
ereport(ERROR,
404+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
405+
errmsg("timestamp out of range")));
406+
407+
/* OK, so mark the cache valid. */
408+
cache_ts = cur_ts;
409+
cache_timezone = session_timezone;
410+
}
365411

366-
timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, fsec,
367-
NULL, NULL);
368-
/* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
412+
*tm = cache_tm;
413+
*fsec = cache_fsec;
369414
if (tzp != NULL)
370-
*tzp = tz;
415+
*tzp = cache_tz;
371416
}
372417

373418

0 commit comments

Comments
 (0)