-
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
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;
}
|
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(). |
@@ -703,6 +704,11 @@ static struct vtm *localtimew(wideval_t timew, struct vtm *result); | |||
|
|||
static int leap_year_p(long y); | |||
#define leap_year_v_p(y) leap_year_p(NUM2LONG(modv((y), INT2FIX(400)))) | |||
static int calc_tm_yday(long tm_year, int tm_mon, int tm_mday); | |||
|
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.
static int calc_wday(int year_mod400, int month, int day); | |
time.c
Outdated
/* Cache key: 15-minute precision */ | ||
typedef struct { | ||
int year; | ||
int month; | ||
int day; | ||
int hour; | ||
int quarter; /* 0-3 representing 00, 15, 30, 45 minute intervals */ | ||
} offset_cache_key; |
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.
I guess it would be possible to pack the cache keys, so that the validness can be checked atomically.
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.
Great idea. I think now we can probably just use our time_t value/15 for our cache key?
I haven't quite convinced myself that it's safe to use 15 minute quarters to cache by..... There are some historical timezones with odd offsets. I don't know of any yet where a dst boundary doesn't happen on a 15 minute boundary in UTC time though.
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.
Perhaps modern time zones do not have such odd UTC offsets, but "zdump(1)" refers to "Europe/Astrahan".
$ LC_TIME=C /bin/date -z Europe/Astrakhan -j {-f,+}%Y-%m-%dT%H:%M:%S%Z 1924-04-30T00:00:00GMT
1924-04-30T03:12:12LMT
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.
I think it'd be OK to abandon such cases, i.e., do not cache if the gmtoff % (15 * 60) != 0
.
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.
I don't think we can use value/15 in general.
E.g. in Newfoundland up until 2011, the daylight savings time change happened at 12:01:00, which is within the same 15-minute chunk of time as 12:00:00 or 12:02:00.
I do think a minute-based cache is generally safe.
configure.ac
Outdated
AC_ARG_ENABLE(macos-localtime-cache, | ||
AS_HELP_STRING([--enable-macos-localtime-cache], | ||
[enable localtime cache optimization for macOS to improve forked process performance]), | ||
[macos_localtime_cache=$enableval], [macos_localtime_cache=no]) | ||
AS_IF([test "$macos_localtime_cache" = yes], [ | ||
AS_CASE(["$target_os"], | ||
[darwin*], [ | ||
AC_DEFINE(ENABLE_MACOS_LOCALTIME_CACHE, 1, [Enable macOS localtime cache optimization]) | ||
AC_MSG_NOTICE([macOS localtime cache optimization enabled]) | ||
], | ||
[AC_MSG_WARN([--enable-macos-localtime-cache is only supported on macOS, ignoring])] | ||
) | ||
]) |
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.
This may not be limited to macOS in the future.
And do you have any reason not to enable it on macOS by the default?
AC_ARG_ENABLE(macos-localtime-cache, | |
AS_HELP_STRING([--enable-macos-localtime-cache], | |
[enable localtime cache optimization for macOS to improve forked process performance]), | |
[macos_localtime_cache=$enableval], [macos_localtime_cache=no]) | |
AS_IF([test "$macos_localtime_cache" = yes], [ | |
AS_CASE(["$target_os"], | |
[darwin*], [ | |
AC_DEFINE(ENABLE_MACOS_LOCALTIME_CACHE, 1, [Enable macOS localtime cache optimization]) | |
AC_MSG_NOTICE([macOS localtime cache optimization enabled]) | |
], | |
[AC_MSG_WARN([--enable-macos-localtime-cache is only supported on macOS, ignoring])] | |
) | |
]) | |
AC_ARG_ENABLE(localtime-cache, | |
AS_HELP_STRING([--enable-localtime-cache], | |
[enable localtime cache optimization to improve forked process performance]), | |
[localtime_cache=$enableval], | |
[AS_CASE(["$target_os"], [darwin*], [localtime_cache=yes], [localtime_cache=no])]) | |
AS_IF([test "$localtime_cache" = yes], [ | |
AC_DEFINE(ENABLE_LOCALTIME_CACHE, 1, [Enable localtime cache optimization]) | |
AC_MSG_NOTICE([localtime cache optimization enabled]) | |
]) |
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.
I checked on Linux and it doesn't seem to have the same performance problem running localtime in a forked process.
29ec151
to
c026384
Compare
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.