From 026375b395fcdc2336666546afd2f21e7ee8bc67 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Wed, 25 Aug 2010 23:00:31 +0100 Subject: [PATCH] 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 --- glib/gdatetime.c | 133 ++++++++++++++++++++++++++++------------- glib/tests/gdatetime.c | 24 +++++--- 2 files changed, 108 insertions(+), 49 deletions(-) diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 9efd4358d..c7972b445 100644 --- a/glib/gdatetime.c +++ b/glib/gdatetime.c @@ -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 diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c index d20471408..b651b5b76 100644 --- a/glib/tests/gdatetime.c +++ b/glib/tests/gdatetime.c @@ -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