1 /* 2 * Copyright (C) 2013 Davidlohr Bueso <davidlohr@hp.com> 3 * 4 * futex-wake: Block a bunch of threads on a futex and wake'em up, N at a time. 5 * 6 * This program is particularly useful to measure the latency of nthread wakeups 7 * in non-error situations: all waiters are queued and all wake calls wakeup 8 * one or more tasks, and thus the waitqueue is never empty. 9 */ 10 11 #include "../perf.h" 12 #include "../util/util.h" 13 #include "../util/stat.h" 14 #include "../util/parse-options.h" 15 #include "../util/header.h" 16 #include "bench.h" 17 #include "futex.h" 18 19 #include <err.h> 20 #include <stdlib.h> 21 #include <sys/time.h> 22 #include <pthread.h> 23 24 /* all threads will block on the same futex */ 25 static u_int32_t futex1 = 0; 26 27 /* 28 * How many wakeups to do at a time. 29 * Default to 1 in order to make the kernel work more. 30 */ 31 static unsigned int nwakes = 1; 32 33 pthread_t *worker; 34 static bool done = false, silent = false; 35 static pthread_mutex_t thread_lock; 36 static pthread_cond_t thread_parent, thread_worker; 37 static struct stats waketime_stats, wakeup_stats; 38 static unsigned int ncpus, threads_starting, nthreads = 0; 39 40 static const struct option options[] = { 41 OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"), 42 OPT_UINTEGER('w', "nwakes", &nwakes, "Specify amount of threads to wake at once"), 43 OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"), 44 OPT_END() 45 }; 46 47 static const char * const bench_futex_wake_usage[] = { 48 "perf bench futex wake <options>", 49 NULL 50 }; 51 52 static void *workerfn(void *arg __maybe_unused) 53 { 54 pthread_mutex_lock(&thread_lock); 55 threads_starting--; 56 if (!threads_starting) 57 pthread_cond_signal(&thread_parent); 58 pthread_cond_wait(&thread_worker, &thread_lock); 59 pthread_mutex_unlock(&thread_lock); 60 61 futex_wait(&futex1, 0, NULL, FUTEX_PRIVATE_FLAG); 62 return NULL; 63 } 64 65 static void print_summary(void) 66 { 67 double waketime_avg = avg_stats(&waketime_stats); 68 double waketime_stddev = stddev_stats(&waketime_stats); 69 unsigned int wakeup_avg = avg_stats(&wakeup_stats); 70 71 printf("Wokeup %d of %d threads in %.4f ms (+-%.2f%%)\n", 72 wakeup_avg, 73 nthreads, 74 waketime_avg/1e3, 75 rel_stddev_stats(waketime_stddev, waketime_avg)); 76 } 77 78 static void block_threads(pthread_t *w, 79 pthread_attr_t thread_attr) 80 { 81 cpu_set_t cpu; 82 unsigned int i; 83 84 threads_starting = nthreads; 85 86 /* create and block all threads */ 87 for (i = 0; i < nthreads; i++) { 88 CPU_ZERO(&cpu); 89 CPU_SET(i % ncpus, &cpu); 90 91 if (pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpu)) 92 err(EXIT_FAILURE, "pthread_attr_setaffinity_np"); 93 94 if (pthread_create(&w[i], &thread_attr, workerfn, NULL)) 95 err(EXIT_FAILURE, "pthread_create"); 96 } 97 } 98 99 static void toggle_done(int sig __maybe_unused, 100 siginfo_t *info __maybe_unused, 101 void *uc __maybe_unused) 102 { 103 done = true; 104 } 105 106 int bench_futex_wake(int argc, const char **argv, 107 const char *prefix __maybe_unused) 108 { 109 int ret = 0; 110 unsigned int i, j; 111 struct sigaction act; 112 pthread_attr_t thread_attr; 113 114 argc = parse_options(argc, argv, options, bench_futex_wake_usage, 0); 115 if (argc) { 116 usage_with_options(bench_futex_wake_usage, options); 117 exit(EXIT_FAILURE); 118 } 119 120 ncpus = sysconf(_SC_NPROCESSORS_ONLN); 121 122 sigfillset(&act.sa_mask); 123 act.sa_sigaction = toggle_done; 124 sigaction(SIGINT, &act, NULL); 125 126 if (!nthreads) 127 nthreads = ncpus; 128 129 worker = calloc(nthreads, sizeof(*worker)); 130 if (!worker) 131 err(EXIT_FAILURE, "calloc"); 132 133 printf("Run summary [PID %d]: blocking on %d threads (at futex %p), " 134 "waking up %d at a time.\n\n", 135 getpid(), nthreads, &futex1, nwakes); 136 137 init_stats(&wakeup_stats); 138 init_stats(&waketime_stats); 139 pthread_attr_init(&thread_attr); 140 pthread_mutex_init(&thread_lock, NULL); 141 pthread_cond_init(&thread_parent, NULL); 142 pthread_cond_init(&thread_worker, NULL); 143 144 for (j = 0; j < bench_repeat && !done; j++) { 145 unsigned int nwoken = 0; 146 struct timeval start, end, runtime; 147 148 /* create, launch & block all threads */ 149 block_threads(worker, thread_attr); 150 151 /* make sure all threads are already blocked */ 152 pthread_mutex_lock(&thread_lock); 153 while (threads_starting) 154 pthread_cond_wait(&thread_parent, &thread_lock); 155 pthread_cond_broadcast(&thread_worker); 156 pthread_mutex_unlock(&thread_lock); 157 158 usleep(100000); 159 160 /* Ok, all threads are patiently blocked, start waking folks up */ 161 gettimeofday(&start, NULL); 162 while (nwoken != nthreads) 163 nwoken += futex_wake(&futex1, nwakes, FUTEX_PRIVATE_FLAG); 164 gettimeofday(&end, NULL); 165 timersub(&end, &start, &runtime); 166 167 update_stats(&wakeup_stats, nwoken); 168 update_stats(&waketime_stats, runtime.tv_usec); 169 170 if (!silent) { 171 printf("[Run %d]: Wokeup %d of %d threads in %.4f ms\n", 172 j + 1, nwoken, nthreads, runtime.tv_usec/1e3); 173 } 174 175 for (i = 0; i < nthreads; i++) { 176 ret = pthread_join(worker[i], NULL); 177 if (ret) 178 err(EXIT_FAILURE, "pthread_join"); 179 } 180 181 } 182 183 /* cleanup & report results */ 184 pthread_cond_destroy(&thread_parent); 185 pthread_cond_destroy(&thread_worker); 186 pthread_mutex_destroy(&thread_lock); 187 pthread_attr_destroy(&thread_attr); 188 189 print_summary(); 190 191 free(worker); 192 return ret; 193 } 194