From be4ecfdf6c8daa75c4df8ac875c9a87da80d81a0 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 8 Dec 2008 14:10:52 +0100 Subject: [PATCH] Add gtod_cpu option for pinning gettimeofday() to a single CPU Similar to what real life products sometimes do, offload gettimeofday() calls to a single CPU and have that update the current time into a shared memory location. This option pins a specific CPU for that job, and excludes it from participating in any of the IO jobs. Signed-off-by: Jens Axboe --- HOWTO | 12 ++++++++++++ fio.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ fio.h | 6 ++++++ gettime.c | 21 ++++++++++++++++++++- init.c | 16 +++++++++++++++- options.c | 16 ++++++++++++++++ os/os-linux.h | 4 ++-- os/os.h | 3 ++- 8 files changed, 122 insertions(+), 5 deletions(-) diff --git a/HOWTO b/HOWTO index 7bd71a9a..731684cb 100644 --- a/HOWTO +++ b/HOWTO @@ -869,6 +869,18 @@ gtod_reduce=bool Enable all of the gettimeofday() reducing options we only do about 0.4% of the gtod() calls we would have done if all time keeping was enabled. +gtod_cpu=int Sometimes it's cheaper to dedicate a single thread of + execution to just getting the current time. Fio (and + databases, for instance) are very intensive on gettimeofday() + calls. With this option, you can set one CPU aside for + doing nothing but logging current time to a shared memory + location. Then the other threads/processes that run IO + workloads need only copy that segment, instead of entering + the kernel with a gettimeofday() call. The CPU set aside + for doing these time calls will be excluded from other + uses. Fio will manually clear it from the CPU mask of other + jobs. + 6.0 Interpreting the output --------------------------- diff --git a/fio.c b/fio.c index 5a87ae4b..a58effcf 100644 --- a/fio.c +++ b/fio.c @@ -55,6 +55,7 @@ static struct fio_mutex *startup_mutex; static volatile int fio_abort; static int exit_value; static struct itimerval itimer; +static pthread_t gtod_thread; struct io_log *agg_io_log[2]; @@ -964,6 +965,18 @@ static void *thread_main(void *data) goto err; } + if (td->o.gtod_cpu) { + if (fio_getaffinity(td->pid, &td->o.cpumask) == -1) { + td_verror(td, errno, "cpu_get_affinity"); + goto err; + } + fio_cpu_clear(&td->o.cpumask, td->o.gtod_cpu); + if (fio_setaffinity(td) == -1) { + td_verror(td, errno, "cpu_set_affinity"); + goto err; + } + } + if (td->ioprio_set) { if (ioprio_set(IOPRIO_WHO_PROCESS, 0, td->ioprio) == -1) { td_verror(td, errno, "ioprio_set"); @@ -1229,6 +1242,39 @@ reaped: terminate_threads(TERMINATE_ALL); } +static void *gtod_thread_main(void *data) +{ + fio_mutex_up(startup_mutex); + + /* + * As long as we have jobs around, update the clock. It would be nice + * to have some way of NOT hammering that CPU with gettimeofday(), + * but I'm not sure what to use outside of a simple CPU nop to relax + * it - we don't want to lose precision. + */ + while (threads) { + fio_gtod_update(); + nop; + } + + return NULL; +} + +static int fio_start_gtod_thread(void) +{ + if (pthread_create(>od_thread, NULL, gtod_thread_main, NULL)) { + perror("Can't create gtod thread"); + return 1; + } + if (pthread_detach(gtod_thread) < 0) { + perror("Can't detatch gtod thread"); + return 1; + } + + fio_mutex_down(startup_mutex); + return 0; +} + /* * Main function for kicking off and reaping jobs, as needed. */ @@ -1241,6 +1287,9 @@ static void run_threads(void) if (fio_pin_memory()) return; + if (fio_gtod_offload && fio_start_gtod_thread()) + return; + if (!terse_output) { printf("Starting "); if (nr_thread) diff --git a/fio.h b/fio.h index f8e6a4a0..d21f5e4b 100644 --- a/fio.h +++ b/fio.h @@ -500,6 +500,8 @@ struct thread_options { unsigned int disable_slat; unsigned int disable_bw; unsigned int gtod_reduce; + unsigned int gtod_cpu; + unsigned int gtod_offload; char *read_iolog_file; char *write_iolog_file; @@ -699,6 +701,8 @@ extern int read_only; extern int eta_print; extern unsigned long done_secs; extern char *job_section; +extern int fio_gtod_offload; +extern int fio_gtod_cpu; extern struct thread_data *threads; @@ -828,6 +832,8 @@ extern void usec_sleep(struct thread_data *, unsigned long); extern void rate_throttle(struct thread_data *, unsigned long, unsigned int); extern void fill_start_time(struct timeval *); extern void fio_gettime(struct timeval *, void *); +extern void fio_gtod_init(void); +extern void fio_gtod_update(void); extern void set_genesis_time(void); extern int ramp_time_over(struct thread_data *); extern int in_ramp_time(struct thread_data *); diff --git a/gettime.c b/gettime.c index 80eeaf14..b1431f32 100644 --- a/gettime.c +++ b/gettime.c @@ -6,6 +6,7 @@ #include #include "fio.h" +#include "smalloc.h" #include "hash.h" @@ -13,6 +14,10 @@ static int clock_gettime_works; static struct timeval last_tv; static int last_tv_valid; +static struct timeval *fio_tv; +int fio_gtod_offload = 0; +int fio_gtod_cpu = -1; + #ifdef FIO_DEBUG_TIME #define HASH_BITS 8 @@ -116,7 +121,10 @@ void fio_gettime(struct timeval *tp, void fio_unused *caller) gtod_log_caller(caller); #endif - if (!clock_gettime_works) { + if (fio_tv) { + memcpy(tp, fio_tv, sizeof(*tp)); + return; + } else if (!clock_gettime_works) { gtod: gettimeofday(tp, NULL); } else { @@ -145,3 +153,14 @@ gtod: last_tv_valid = 1; memcpy(&last_tv, tp, sizeof(*tp)); } + +void fio_gtod_init(void) +{ + fio_tv = smalloc(sizeof(struct timeval)); + assert(fio_tv); +} + +void fio_gtod_update(void) +{ + gettimeofday(fio_tv, NULL); +} diff --git a/init.c b/init.c index f00ced32..a8acdc04 100644 --- a/init.c +++ b/init.c @@ -27,7 +27,7 @@ static char **ini_file; static int max_jobs = MAX_JOBS; static int dump_cmdline; -struct thread_data def_thread; +static struct thread_data def_thread; struct thread_data *threads = NULL; int exitall_on_terminate = 0; @@ -214,6 +214,14 @@ static int fixup_options(struct thread_data *td) { struct thread_options *o = &td->o; +#ifndef FIO_HAVE_CPU_AFFINITY + if (td->o.gtod_cpu) { + log_err("fio: platform must support CPU affinity for" + "gettimeofday() offloading\n"); + return 1; + } +#endif + if (read_only && td_write(td)) { log_err("fio: job <%s> has write bit set, but fio is in" " read-only mode\n", td->o.name); @@ -1104,5 +1112,11 @@ int parse_options(int argc, char *argv[]) return 1; } + if (def_thread.o.gtod_offload) { + fio_gtod_init(); + fio_gtod_offload = 1; + fio_gtod_cpu = def_thread.o.gtod_cpu; + } + return 0; } diff --git a/options.c b/options.c index 1953e3d6..5bbeb342 100644 --- a/options.c +++ b/options.c @@ -497,6 +497,16 @@ static int str_gtod_reduce_cb(void *data, int *il) return 0; } +static int str_gtod_cpu_cb(void *data, int *il) +{ + struct thread_data *td = data; + int val = *il; + + td->o.gtod_cpu = val; + td->o.gtod_offload = 1; + return 0; +} + #define __stringify_1(x) #x #define __stringify(x) __stringify_1(x) @@ -1388,6 +1398,12 @@ static struct fio_option options[] = { .parent = "gtod_reduce", .def = "0", }, + { + .name = "gtod_cpu", + .type = FIO_OPT_INT, + .cb = str_gtod_cpu_cb, + .help = "Setup dedicated gettimeofday() thread on this CPU", + }, { .name = NULL, }, diff --git a/os/os-linux.h b/os/os-linux.h index c0f53276..6812acd2 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -54,7 +54,6 @@ typedef struct drand48_data os_random_state_t; * If you are on an ancient glibc (2.3.2), then define GLIBC_2_3_2 if you want * the affinity helpers to work. */ -#ifdef FIO_HAVE_CPU_AFFINITY #ifndef GLIBC_2_3_2 #define fio_setaffinity(td) \ sched_setaffinity((td)->pid, sizeof((td)->o.cpumask), &(td)->o.cpumask) @@ -66,7 +65,8 @@ typedef struct drand48_data os_random_state_t; #define fio_getaffinity(pid, ptr) \ sched_getaffinity((pid), (ptr)) #endif -#endif + +#define fio_cpu_clear(mask, cpu) CPU_CLR((cpu), (mask)) static inline int ioprio_set(int which, int who, int ioprio) { diff --git a/os/os.h b/os/os.h index 1cff4945..823b039a 100644 --- a/os/os.h +++ b/os/os.h @@ -40,7 +40,8 @@ #ifndef FIO_HAVE_CPU_AFFINITY #define fio_setaffinity(td) (0) -#define fio_getaffinity(pid, mask) do { } while(0) +#define fio_getaffinity(pid, mask) do { } while (0) +#define fio_cpu_clear(mask, cpu) do { } while (0) #endif #ifndef FIO_HAVE_IOPRIO -- 2.25.1