-
Notifications
You must be signed in to change notification settings - Fork 5.4k
experiment with speeding up Time.local #13968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
deefc8c
to
e2066b2
Compare
e2066b2
to
e8595b9
Compare
That benchmark looks including Rather, I feel that this problem should be reported to Apple. #include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
void
localtime_benchmark(const char *cond)
{
struct timespec ts, te;
clock_gettime(CLOCK_MONOTONIC , &ts);
for (long n = 0; n < 10000; ++n) {
time_t lt = 1753075380;
struct tm t;
localtime_r(<, &t);
}
clock_gettime(CLOCK_MONOTONIC , &te);
time_t diff = te.tv_sec - ts.tv_sec;
unsigned long ndiff = te.tv_nsec;
if (te.tv_nsec < ts.tv_nsec) {
--diff;
ndiff += 1000000000UL;
}
ndiff -= ts.tv_nsec;
printf("%s: %ld.%.9lu\n", cond, diff, ndiff);
}
int
main(void)
{
localtime_benchmark("non-forked");
pid_t forked = fork();
if (forked) {
int stat;
waitpid(forked, &stat, 0);
}
else {
localtime_benchmark(" forked");
}
return 0;
}
|
time.c
Outdated
/* Days in each month (non-leap year) */ | ||
static const int days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | ||
|
||
/* Check if year is a leap year */ | ||
static inline int | ||
is_leap_year(int year) | ||
{ | ||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); | ||
} | ||
|
||
/* Get days in month, accounting for leap years */ | ||
static inline int | ||
get_days_in_month(int year, int month) | ||
{ | ||
if (month == 2 && is_leap_year(year)) { | ||
return 29; | ||
} | ||
return days_in_month[month - 1]; | ||
} | ||
|
||
/* Calculate day of week from year/month/day */ | ||
static int | ||
calculate_wday(int year, int month, int day) | ||
{ | ||
/* Zeller's congruence algorithm */ | ||
if (month < 3) { | ||
month += 12; | ||
year--; | ||
} | ||
int k = year % 100; | ||
int j = year / 100; | ||
int h = (day + (13 * (month + 1)) / 5 + k + k / 4 + j / 4 - 2 * j) % 7; | ||
/* Convert to tm_wday format (Sunday = 0) */ | ||
return (h + 6) % 7; | ||
} | ||
|
||
/* Calculate day of year from year/month/day */ | ||
static int | ||
calculate_yday(int year, int month, int day) | ||
{ | ||
int yday = 0; | ||
for (int m = 1; m < month; m++) { | ||
yday += get_days_in_month(year, m); | ||
} | ||
return yday + day - 1; /* tm_yday is 0-based */ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These look like duplicates to the existing functions.
Indeed. I did some searching around and found a few references which make me think this won't be fixed: https://developer.apple.com/forums/thread/747499 And from fork's manpage:
AIUI there is IPC signaling in place to notify processes of timezone changes, and these don't work after fork() before exec(). |
On macOS, methods like
Time.local
can be up to hundreds of times slower when run in a forked child process than when run in the parent process. This simple benchmark demonstrates the issue:This impacts all kinds of things from logging, parsing times, working with zipfiles with
rubyzip
, etc.This PR implements a small cache for offsets from UTC for any given 15-minute quarters derived from the value passed to
rb_localtime_r
- this avoids multiple calls to libc'slocaltime_r
for the same time values within the same 15-minute block of time. As far as I can tell there are no timezones past or present which have an offset that doesn't fall on a 15-minute boundary (e.g. +0/15/30/45 minutes).The cache is cleared if the timezone is changed.