1、背景
继上周《Linux下mktime耗时测试》,本次通过参考glibc、内核源码进行实验
2、接口说明
时间转换接口,主要用于所指向的结构转换为自 1970 年1月1日以来持续时间的秒数,发生错误时返回-1。
SYNOPSIS top
#include <time.h>
time_t mktime(struct tm *timeptr);
DESCRIPTION
The functionality described on this reference page is aligned
with the ISO C standard. Any conflict between the requirements
described here and the ISO C standard is unintentional. This
volume of POSIX.1‐2017 defers to the ISO C standard.
The mktime() function shall convert the broken-down time,
expressed as local time, in the structure pointed to by timeptr,
into a time since the Epoch value with the same encoding as that
of the values returned by time(). The original values of the
tm_wday and tm_yday components of the structure shall be ignored,
and the original values of the other components shall not be
restricted to the ranges described in <time.h>.
---
Broken-down time is stored in the structure tm, which is defined in <time.h> as follows:
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
3、实现
3.1 参考内核实现(最简单)
核心接口实现,几行代码就搞定
/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
* Assumes input in normal date format, i.e. 1980-12-31 23:59:59
* => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
*
* [For the Julian calendar (which was used in Russia before 1917,
* Britain & colonies before 1752, anywhere else before 1582,
* and is still in use by some communities) leave out the
* -year/100+year/400 terms, and add 10.]
*
* This algorithm was first published by Gauss (I think).
*
* WARNING: this function will overflow on 2106-02-07 06:28:16 on
* machines where long is 32-bit! (However, as time_t is signed, we
* will already get problems at other places on 2038-01-19 03:14:08)
*/
unsigned long __mktime_kernel(const unsigned int year0, const unsigned int mon0,
const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec)
{
unsigned int mon = mon0, year = year0;
/* 1..12 -> 11,12,1..10 */
if (0 >= (int)(mon -= 2)) {
mon += 12; /* Puts Feb last since it has leap day */
year -= 1;
}
return ((((unsigned long)
(year / 4 - year / 100 + year / 400 + 367 * mon / 12 + day) +
year * 365 - 719499
) * 24 + hour /* now have hours */
) * 60 + min /* now have minutes */
) * 60 + sec; /* finally seconds */
}
然后再仔细看他调用的地方,发现时间如果是太小了得有个+100的修正;
time_t sdk_mktime_kernel(struct tm *tp)
{
if (!tp) {
printf("NULL\n");
return -1;
}
if (unlikely(tp->tm_year + 1900 < 1970)) {
tp->tm_year += 100;
}
return __mktime_kernel(tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
3.2 glibc实现参考(裁剪)
核心实现是 __mktime_internal,然后这里面处理比较复杂,就是拆解了一些:
1、去掉了一个夏令时DST相关的逻辑;
2、去掉了一个闰秒相关的逻辑;
#define EPOCH_YEAR 1970
#define TM_YEAR_BASE 1900
#if INT_MAX <= (LONG_MAX / 4 / 366 / 24 / 60 / 60)
typedef long int long_int;
#else
typedef long long int long_int;
#endif
static const unsigned short int __g_mon_yday[2][13] = {
/* Normal years. */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years. */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};
#ifdef __cplusplus
extern "C" {
#endif
/* Is YEAR + TM_YEAR_BASE a leap year? */
static inline bool __is_leapyear(long_int year)
{
/* Don't add YEAR to TM_YEAR_BASE, as that might overflow.
Also, work even if YEAR is negative. */
return ((year & 3) == 0 &&
(year % 100 != 0 || ((year / 100) & 3) == (- (TM_YEAR_BASE / 100) & 3)));
}
/* Shift A right by B bits portably, by dividing A by 2**B and
truncating towards minus infinity. B should be in the range 0 <= B
<= long_BITS - 2, where long_BITS is the number of useful
bits in a long. long_BITS is at least 32.
ISO C99 says that A >> B is implementation-defined if A < 0. Some
implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
right in the usual way when A < 0, so SHR falls back on division if
ordinary A >> B doesn't seem to be the usual signed shift. */
static inline long_int __shr(long_int a, int b)
{
long_int one = 1;
return ((-one >> 1 == -1) ? (a >> b) : (a + (a < 0)) / (one << b) - (a < 0));
}
static inline long_int __do_ydhms_diff(long_int year1, long_int yday1, int hour1, int min1,
int sec1,
long_int year0, long_int yday0, int hour0, int min0, int sec0)
{
// verify(-1 / 2 == 0);
/* Compute intervening leap days correctly even if year is negative.
Take care to avoid integer overflow here. */
int a4 = __shr(year1, 2) + __shr(TM_YEAR_BASE, 2) - !(year1 & 3);
int b4 = __shr(year0, 2) + __shr(TM_YEAR_BASE, 2) - !(year0 & 3);
int a100 = (a4 + (a4 < 0)) / 25 - (a4 < 0);
int b100 = (b4 + (b4 < 0)) / 25 - (b4 < 0);
int a400 = __shr(a100, 2);
int b400 = __shr(b100, 2);
int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
/* Compute the desired time without overflowing. */
long_int years = year1 - year0;
long_int days = 365 * years + yday1 - yday0 + intervening_leap_days;
long_int hours = 24 * days + hour1 - hour0;
long_int minutes = 60 * hours + min1 - min0;
long_int seconds = 60 * minutes + sec1 - sec0;
return seconds;
}
/* Convert *TP to a __time64_t value, inverting
the monotonic and mostly-unit-linear conversion function CONVERT.
Use *OFFSET to keep track of a guess at the offset of the result,
compared to what the result would be for UTC without leap seconds.
If *OFFSET's guess is correct, only one CONVERT call is needed.
If successful, set *TP to the canonicalized struct tm;
otherwise leave *TP alone, return ((time_t) -1) and set errno.
This function is external because it is used also by timegm.c. */
static inline time_t __mktime_internal(struct tm *tp)
{
/* The maximum number of probes (calls to CONVERT) should be enough
to handle any combinations of time zone rule changes, solar time,
leap seconds, and oscillations around a spring-forward gap.
POSIX.1 prohibits leap seconds, but some hosts have them anyway. */
// int remaining_probes = 6;
/* Time requested. Copy it in case CONVERT modifies *TP; this can
occur if TP is localtime's returned value and CONVERT is localtime. */
int sec = tp->tm_sec;
int min = tp->tm_min;
int hour = tp->tm_hour;
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
int negative_mon_remainder = mon_remainder < 0;
int mon_years = mon / 12 - negative_mon_remainder;
long_int lyear_requested = year_requested;
long_int year = lyear_requested + mon_years;
/* The other values need not be in range:
the remaining code handles overflows correctly. */
/* Calculate day of year from year, month, and day of month.
The result need not be in range. */
int mon_yday = ((__g_mon_yday[__is_leapyear(year)]
[mon_remainder + 12 * negative_mon_remainder]) - 1);
long_int lmday = mday;
long_int yday = mon_yday + lmday;
if (tp->tm_isdst != 0) {
printf("WARN: DST not support !!!\n");
}
/* skip LEAP_SECONDS_POSSIBLE &
* skip NEGATIVE_OFFSET_GUESS
* skip tm.tm_isdst */
return (time_t)__do_ydhms_diff(year, yday, hour, min, sec,
EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, 0);
}
最后封装统一了一下格式
time_t sdk_mktime_glibc(struct tm *tp)
{
if (!tp) {
printf("NULL\n");
return -1;
}
return __mktime_internal(tp);
}
3.3 实验结果
2021-09-26 20:24:09
Running ./bm_mktime
Run on (16 X 2199.09 MHz CPU s)
CPU Caches:
L1 Data 32K (x4)
L1 Instruction 32K (x4)
L2 Unified 256K (x4)
L3 Unified 25600K (x4)
Load Average: 0.00, 0.00, 0.00
----------------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------------
bm_mktime/mktime_TZ_NULL_isDST 886 ns 886 ns 787062
bm_mktime/mktime_TZ_NULL_noDST 886 ns 886 ns 789607
bm_mktime/mktime_TZ_empty_isDST 406 ns 406 ns 1723817
bm_mktime/mktime_TZ_empty_noDST 406 ns 406 ns 1727658
bm_mktime/mktime_TZ_shanghai_isDST 405 ns 405 ns 1721386
bm_mktime/mktime_TZ_shanghai_noDST 406 ns 406 ns 1725817
bm_mktime/sdk_mktime_glibc 18.4 ns 18.4 ns 38107389
bm_mktime/sdk_mktime_kernel 18.4 ns 18.4 ns 38096369
4、结论
实验结果看,用户态直接计算,提升呈几何增长;glibc、kernel实现测试的速度不相上下;但实现复杂度来看,kernel版本更加简洁。