datetime: Avoid excessive copies in add_full()

The current implementation of g_date_time_add_full() creates multiple
GDateTime temporary objects and unrefs them immediately; even with the
slice allocator this could result in a performance bottleneck,
especially if the atomic integer operations fall back to slow paths.

We can isolate the components of the add_full() operation and create
internal modifiers that operate on an existing GDateTime; this brings
down the number of GDateTime copies created from six to one.

While at it, the test suite for add_full() should have more checks for
roll-over of months and days.

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
This commit is contained in:
Emmanuele Bassi 2010-08-25 23:00:31 +01:00
parent 0d0a9bb448
commit 026375b395
2 changed files with 108 additions and 49 deletions

View File

@ -277,6 +277,24 @@ get_weekday_name_abbr (gint day)
return NULL;
}
static inline gint
date_to_julian (gint year,
gint month,
gint day)
{
gint a = (14 - month) / 12;
gint y = year + 4800 - a;
gint m = month + (12 * a) - 3;
return day
+ (((153 * m) + 2) / 5)
+ (y * 365)
+ (y / 4)
- (y / 100)
+ (y / 400)
- 32045;
}
static inline void
g_date_time_add_days_internal (GDateTime *datetime,
gint64 days)
@ -315,6 +333,69 @@ g_date_time_add_usec (GDateTime *datetime,
datetime->usec = __usec % USEC_PER_DAY;
}
/*< internal >
* g_date_time_add_dmy:
* @datetime: a #GDateTime
* @years: years to add, in the Gregorian calendar
* @months: months to add, in the Gregorian calendar
* @days: days to add, in the Gregorian calendar
*
* Updates @datetime by adding @years, @months and @days to it
*
* This function modifies the passed #GDateTime so public accessors
* should make always pass a copy
*/
static inline void
g_date_time_add_dmy (GDateTime *datetime,
gint years,
gint months,
gint days)
{
gint __year = g_date_time_get_year (datetime);
gint __month = g_date_time_get_month (datetime);
gint __day = g_date_time_get_day_of_month (datetime);
gint step, i;
const guint16 *max_days;
/* subtract one day for leap years */
if (GREGORIAN_LEAP (__year) && __month == 2)
{
if (__day == 29)
__day -= 1;
}
__year += years;
/* add months */
step = months > 0 ? 1 : -1;
for (i = 0; i < ABS (months); i++)
{
__month += step;
if (__month < 1)
{
__year -= 1;
__month = 12;
}
else if (__month > 12)
{
__year += 1;
__month = 1;
}
}
/* clamp the days */
max_days = days_in_months[GREGORIAN_LEAP (__year) ? 1 : 0];
if (max_days[__month] < __day)
__day = max_days[__month];
/* since the add_days_internal() uses the julian date we need to
* update it using the new year/month/day and then add the days
*/
datetime->julian = date_to_julian (__year, __month, __day);
g_date_time_add_days_internal (datetime, days);
}
#define ZONEINFO_DIR "zoneinfo"
#define TZ_MAGIC "TZif"
#define TZ_MAGIC_LEN (strlen (TZ_MAGIC))
@ -970,31 +1051,21 @@ g_date_time_add_full (const GDateTime *datetime,
gint minutes,
gint seconds)
{
GDateTime *tmp, *dt;
GDateTime *dt;
gint64 usecs;
g_return_val_if_fail (datetime != NULL, NULL);
dt = g_date_time_add_years (datetime, years);
tmp = dt;
dt = g_date_time_copy (datetime);
dt = g_date_time_add_months (tmp, months);
g_date_time_unref (tmp);
tmp = dt;
/* add date */
g_date_time_add_dmy (dt, years, months, days);
dt = g_date_time_add_days (tmp, days);
g_date_time_unref (tmp);
tmp = dt;
dt = g_date_time_add_hours (tmp, hours);
g_date_time_unref (tmp);
tmp = dt;
dt = g_date_time_add_minutes (tmp, minutes);
g_date_time_unref (tmp);
tmp = dt;
dt = g_date_time_add_seconds (tmp, seconds);
g_date_time_unref (tmp);
/* add time */
usecs = (hours * USEC_PER_HOUR)
+ (minutes * USEC_PER_MINUTE)
+ (seconds * USEC_PER_SECOND);
g_date_time_add_usec (dt, usecs);
return dt;
}
@ -1144,8 +1215,8 @@ g_date_time_equal (gconstpointer dt1,
a = dt1;
b = dt2;
a_utc = g_date_time_to_utc ((GDateTime *) a);
b_utc = g_date_time_to_utc ((GDateTime *) b);
a_utc = g_date_time_to_utc (a);
b_utc = g_date_time_to_utc (b);
a_epoch = g_date_time_to_epoch (a_utc);
b_epoch = g_date_time_to_epoch (b_utc);
@ -1573,24 +1644,6 @@ g_date_time_is_daylight_savings (const GDateTime *datetime)
return datetime->tz->is_dst;
}
static inline gint
date_to_julian (gint year,
gint month,
gint day)
{
gint a = (14 - month) / 12;
gint y = year + 4800 - a;
gint m = month + (12 * a) - 3;
return day
+ (((153 * m) + 2) / 5)
+ (y * 365)
+ (y / 4)
- (y / 100)
+ (y / 400)
- 32045;
}
/**
* g_date_time_new_from_date:
* @year: the Gregorian year

View File

@ -543,15 +543,21 @@ test_GDateTime_add_full (void)
g_date_time_unref (dt2); \
} G_STMT_END
TEST_ADD_FULL (2009, 10, 21, 0, 0, 0,
1, 1, 1, 1, 1, 1,
2010, 11, 22, 1, 1, 1);
TEST_ADD_FULL (2000, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0,
2000, 2, 1, 1, 1, 1);
TEST_ADD_FULL (2000, 1, 1, 0, 0, 0,
-1, 1, 0, 0, 0, 0,
1999, 2, 1, 0, 0, 0);
TEST_ADD_FULL (2009, 10, 21, 0, 0, 0,
1, 1, 1, 1, 1, 1,
2010, 11, 22, 1, 1, 1);
TEST_ADD_FULL (2000, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0,
2000, 2, 1, 1, 1, 1);
TEST_ADD_FULL (2000, 1, 1, 0, 0, 0,
-1, 1, 0, 0, 0, 0,
1999, 2, 1, 0, 0, 0);
TEST_ADD_FULL (2010, 10, 31, 0, 0, 0,
0, 4, 0, 0, 0, 0,
2011, 2, 28, 0, 0, 0);
TEST_ADD_FULL (2010, 8, 25, 22, 45, 0,
0, 1, 6, 1, 25, 0,
2010, 10, 2, 0, 10, 0);
}
static void