From 5c30a6d2b805ae9b5dd0ad003b9ee86b8965bc47 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 20 Jul 2017 15:46:05 +0200 Subject: [PATCH] process-util: add getpid_cached() as a caching wrapper for getpid() Let's make getpid() fast again. --- src/basic/process-util.c | 62 ++++++++++++++++++++++++++++++++++++++++++++ src/basic/process-util.h | 2 ++ src/test/test-process-util.c | 57 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index b80caca..98adee6 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -922,6 +922,68 @@ int ioprio_parse_priority(const char *s, int *ret) { return 0; } +/* The cached PID, possible values: + * + * == UNSET [0] → cache not initialized yet + * == BUSY [-1] → some thread is initializing it at the moment + * any other → the cached PID + */ + +#define CACHED_PID_UNSET ((pid_t) 0) +#define CACHED_PID_BUSY ((pid_t) -1) + +static pid_t cached_pid = CACHED_PID_UNSET; + +static void reset_cached_pid(void) { + /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */ + cached_pid = CACHED_PID_UNSET; +} + +/* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc + * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against + * libpthread, as it is part of glibc anyway. */ +extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void * __dso_handle); +extern void* __dso_handle __attribute__ ((__weak__)); + +pid_t getpid_cached(void) { + pid_t current_value; + + /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a + * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally + * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when + * objects were used across fork()s. With this caching the old behaviour is somewhat restored. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=1443976 + * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=1d2bc2eae969543b89850e35e532f3144122d80a + */ + + current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY); + + switch (current_value) { + + case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */ + pid_t new_pid; + + new_pid = getpid(); + + if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) { + /* OOM? Let's try again later */ + cached_pid = CACHED_PID_UNSET; + return new_pid; + } + + cached_pid = new_pid; + return new_pid; + } + + case CACHED_PID_BUSY: /* Somebody else is currently initializing */ + return getpid(); + + default: /* Properly initialized */ + return current_value; + } +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 28d8d74..17746b4 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -119,3 +119,5 @@ static inline bool ioprio_priority_is_valid(int i) { } int ioprio_parse_priority(const char *s, int *ret); + +pid_t getpid_cached(void); diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index c5edbcc..f78a8c0 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -410,6 +410,61 @@ static void test_rename_process(void) { test_rename_process_one("1234567", 1); /* should always fit */ } +static void test_getpid_cached(void) { + siginfo_t si; + pid_t a, b, c, d, e, f, child; + + a = raw_getpid(); + b = getpid_cached(); + c = getpid(); + + assert_se(a == b && a == c); + + child = fork(); + assert_se(child >= 0); + + if (child == 0) { + /* In child */ + a = raw_getpid(); + b = getpid_cached(); + c = getpid(); + + assert_se(a == b && a == c); + _exit(0); + } + + d = raw_getpid(); + e = getpid_cached(); + f = getpid(); + + assert_se(a == d && a == e && a == f); + + assert_se(wait_for_terminate(child, &si) >= 0); + assert_se(si.si_status == 0); + assert_se(si.si_code == CLD_EXITED); +} + +#define MEASURE_ITERATIONS (10000000LLU) + +static void test_getpid_measure(void) { + unsigned long long i; + usec_t t, q; + + t = now(CLOCK_MONOTONIC); + for (i = 0; i < MEASURE_ITERATIONS; i++) + (void) getpid(); + q = now(CLOCK_MONOTONIC) - t; + + log_info(" glibc getpid(): %llu/s\n", (unsigned long long) (MEASURE_ITERATIONS*USEC_PER_SEC/q)); + + t = now(CLOCK_MONOTONIC); + for (i = 0; i < MEASURE_ITERATIONS; i++) + (void) getpid_cached(); + q = now(CLOCK_MONOTONIC) - t; + + log_info("getpid_cached(): %llu/s\n", (unsigned long long) (MEASURE_ITERATIONS*USEC_PER_SEC/q)); +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); @@ -434,6 +489,8 @@ int main(int argc, char *argv[]) { test_personality(); test_get_process_cmdline_harder(); test_rename_process(); + test_getpid_cached(); + test_getpid_measure(); return 0; } -- 2.7.4