1 /* 2 * Context switch microbenchmark. 3 * 4 * Copyright (C) 2015 Anton Blanchard <anton@au.ibm.com>, IBM 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12 #define _GNU_SOURCE 13 #include <sched.h> 14 #include <string.h> 15 #include <stdio.h> 16 #include <unistd.h> 17 #include <stdlib.h> 18 #include <getopt.h> 19 #include <signal.h> 20 #include <assert.h> 21 #include <pthread.h> 22 #include <limits.h> 23 #include <sys/time.h> 24 #include <sys/syscall.h> 25 #include <sys/types.h> 26 #include <sys/shm.h> 27 #include <linux/futex.h> 28 29 #include "../utils.h" 30 31 static unsigned int timeout = 30; 32 33 static int touch_vdso; 34 struct timeval tv; 35 36 static int touch_fp = 1; 37 double fp; 38 39 static int touch_vector = 1; 40 typedef int v4si __attribute__ ((vector_size (16))); 41 v4si a, b, c; 42 43 #ifdef __powerpc__ 44 static int touch_altivec = 1; 45 46 static void __attribute__((__target__("no-vsx"))) altivec_touch_fn(void) 47 { 48 c = a + b; 49 } 50 #endif 51 52 static void touch(void) 53 { 54 if (touch_vdso) 55 gettimeofday(&tv, NULL); 56 57 if (touch_fp) 58 fp += 0.1; 59 60 #ifdef __powerpc__ 61 if (touch_altivec) 62 altivec_touch_fn(); 63 #endif 64 65 if (touch_vector) 66 c = a + b; 67 68 asm volatile("# %0 %1 %2": : "r"(&tv), "r"(&fp), "r"(&c)); 69 } 70 71 static void start_thread_on(void *(*fn)(void *), void *arg, unsigned long cpu) 72 { 73 pthread_t tid; 74 cpu_set_t cpuset; 75 pthread_attr_t attr; 76 77 CPU_ZERO(&cpuset); 78 CPU_SET(cpu, &cpuset); 79 80 pthread_attr_init(&attr); 81 82 if (pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset)) { 83 perror("pthread_attr_setaffinity_np"); 84 exit(1); 85 } 86 87 if (pthread_create(&tid, &attr, fn, arg)) { 88 perror("pthread_create"); 89 exit(1); 90 } 91 } 92 93 static void start_process_on(void *(*fn)(void *), void *arg, unsigned long cpu) 94 { 95 int pid; 96 cpu_set_t cpuset; 97 98 pid = fork(); 99 if (pid == -1) { 100 perror("fork"); 101 exit(1); 102 } 103 104 if (pid) 105 return; 106 107 CPU_ZERO(&cpuset); 108 CPU_SET(cpu, &cpuset); 109 110 if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) { 111 perror("sched_setaffinity"); 112 exit(1); 113 } 114 115 fn(arg); 116 117 exit(0); 118 } 119 120 static unsigned long iterations; 121 static unsigned long iterations_prev; 122 123 static void sigalrm_handler(int junk) 124 { 125 unsigned long i = iterations; 126 127 printf("%ld\n", i - iterations_prev); 128 iterations_prev = i; 129 130 if (--timeout == 0) 131 kill(0, SIGUSR1); 132 133 alarm(1); 134 } 135 136 static void sigusr1_handler(int junk) 137 { 138 exit(0); 139 } 140 141 struct actions { 142 void (*setup)(int, int); 143 void *(*thread1)(void *); 144 void *(*thread2)(void *); 145 }; 146 147 #define READ 0 148 #define WRITE 1 149 150 static int pipe_fd1[2]; 151 static int pipe_fd2[2]; 152 153 static void pipe_setup(int cpu1, int cpu2) 154 { 155 if (pipe(pipe_fd1) || pipe(pipe_fd2)) 156 exit(1); 157 } 158 159 static void *pipe_thread1(void *arg) 160 { 161 signal(SIGALRM, sigalrm_handler); 162 alarm(1); 163 164 while (1) { 165 assert(read(pipe_fd1[READ], &c, 1) == 1); 166 touch(); 167 168 assert(write(pipe_fd2[WRITE], &c, 1) == 1); 169 touch(); 170 171 iterations += 2; 172 } 173 174 return NULL; 175 } 176 177 static void *pipe_thread2(void *arg) 178 { 179 while (1) { 180 assert(write(pipe_fd1[WRITE], &c, 1) == 1); 181 touch(); 182 183 assert(read(pipe_fd2[READ], &c, 1) == 1); 184 touch(); 185 } 186 187 return NULL; 188 } 189 190 static struct actions pipe_actions = { 191 .setup = pipe_setup, 192 .thread1 = pipe_thread1, 193 .thread2 = pipe_thread2, 194 }; 195 196 static void yield_setup(int cpu1, int cpu2) 197 { 198 if (cpu1 != cpu2) { 199 fprintf(stderr, "Both threads must be on the same CPU for yield test\n"); 200 exit(1); 201 } 202 } 203 204 static void *yield_thread1(void *arg) 205 { 206 signal(SIGALRM, sigalrm_handler); 207 alarm(1); 208 209 while (1) { 210 sched_yield(); 211 touch(); 212 213 iterations += 2; 214 } 215 216 return NULL; 217 } 218 219 static void *yield_thread2(void *arg) 220 { 221 while (1) { 222 sched_yield(); 223 touch(); 224 } 225 226 return NULL; 227 } 228 229 static struct actions yield_actions = { 230 .setup = yield_setup, 231 .thread1 = yield_thread1, 232 .thread2 = yield_thread2, 233 }; 234 235 static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout, 236 void *addr2, int val3) 237 { 238 return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3); 239 } 240 241 static unsigned long cmpxchg(unsigned long *p, unsigned long expected, 242 unsigned long desired) 243 { 244 unsigned long exp = expected; 245 246 __atomic_compare_exchange_n(p, &exp, desired, 0, 247 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); 248 return exp; 249 } 250 251 static unsigned long xchg(unsigned long *p, unsigned long val) 252 { 253 return __atomic_exchange_n(p, val, __ATOMIC_SEQ_CST); 254 } 255 256 static int mutex_lock(unsigned long *m) 257 { 258 int c; 259 260 c = cmpxchg(m, 0, 1); 261 if (!c) 262 return 0; 263 264 if (c == 1) 265 c = xchg(m, 2); 266 267 while (c) { 268 sys_futex(m, FUTEX_WAIT, 2, NULL, NULL, 0); 269 c = xchg(m, 2); 270 } 271 272 return 0; 273 } 274 275 static int mutex_unlock(unsigned long *m) 276 { 277 if (*m == 2) 278 *m = 0; 279 else if (xchg(m, 0) == 1) 280 return 0; 281 282 sys_futex(m, FUTEX_WAKE, 1, NULL, NULL, 0); 283 284 return 0; 285 } 286 287 static unsigned long *m1, *m2; 288 289 static void futex_setup(int cpu1, int cpu2) 290 { 291 int shmid; 292 void *shmaddr; 293 294 shmid = shmget(IPC_PRIVATE, getpagesize(), SHM_R | SHM_W); 295 if (shmid < 0) { 296 perror("shmget"); 297 exit(1); 298 } 299 300 shmaddr = shmat(shmid, NULL, 0); 301 if (shmaddr == (char *)-1) { 302 perror("shmat"); 303 shmctl(shmid, IPC_RMID, NULL); 304 exit(1); 305 } 306 307 shmctl(shmid, IPC_RMID, NULL); 308 309 m1 = shmaddr; 310 m2 = shmaddr + sizeof(*m1); 311 312 *m1 = 0; 313 *m2 = 0; 314 315 mutex_lock(m1); 316 mutex_lock(m2); 317 } 318 319 static void *futex_thread1(void *arg) 320 { 321 signal(SIGALRM, sigalrm_handler); 322 alarm(1); 323 324 while (1) { 325 mutex_lock(m2); 326 mutex_unlock(m1); 327 328 iterations += 2; 329 } 330 331 return NULL; 332 } 333 334 static void *futex_thread2(void *arg) 335 { 336 while (1) { 337 mutex_unlock(m2); 338 mutex_lock(m1); 339 } 340 341 return NULL; 342 } 343 344 static struct actions futex_actions = { 345 .setup = futex_setup, 346 .thread1 = futex_thread1, 347 .thread2 = futex_thread2, 348 }; 349 350 static int processes; 351 352 static struct option options[] = { 353 { "test", required_argument, 0, 't' }, 354 { "process", no_argument, &processes, 1 }, 355 { "timeout", required_argument, 0, 's' }, 356 { "vdso", no_argument, &touch_vdso, 1 }, 357 { "no-fp", no_argument, &touch_fp, 0 }, 358 #ifdef __powerpc__ 359 { "no-altivec", no_argument, &touch_altivec, 0 }, 360 #endif 361 { "no-vector", no_argument, &touch_vector, 0 }, 362 { 0, }, 363 }; 364 365 static void usage(void) 366 { 367 fprintf(stderr, "Usage: context_switch2 <options> CPU1 CPU2\n\n"); 368 fprintf(stderr, "\t\t--test=X\tpipe, futex or yield (default)\n"); 369 fprintf(stderr, "\t\t--process\tUse processes (default threads)\n"); 370 fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n"); 371 fprintf(stderr, "\t\t--vdso\t\ttouch VDSO\n"); 372 fprintf(stderr, "\t\t--fp\t\ttouch FP\n"); 373 #ifdef __powerpc__ 374 fprintf(stderr, "\t\t--altivec\ttouch altivec\n"); 375 #endif 376 fprintf(stderr, "\t\t--vector\ttouch vector\n"); 377 } 378 379 int main(int argc, char *argv[]) 380 { 381 signed char c; 382 struct actions *actions = &yield_actions; 383 int cpu1; 384 int cpu2; 385 static void (*start_fn)(void *(*fn)(void *), void *arg, unsigned long cpu); 386 387 while (1) { 388 int option_index = 0; 389 390 c = getopt_long(argc, argv, "", options, &option_index); 391 392 if (c == -1) 393 break; 394 395 switch (c) { 396 case 0: 397 if (options[option_index].flag != 0) 398 break; 399 400 usage(); 401 exit(1); 402 break; 403 404 case 't': 405 if (!strcmp(optarg, "pipe")) { 406 actions = &pipe_actions; 407 } else if (!strcmp(optarg, "yield")) { 408 actions = &yield_actions; 409 } else if (!strcmp(optarg, "futex")) { 410 actions = &futex_actions; 411 } else { 412 usage(); 413 exit(1); 414 } 415 break; 416 417 case 's': 418 timeout = atoi(optarg); 419 break; 420 421 default: 422 usage(); 423 exit(1); 424 } 425 } 426 427 if (processes) 428 start_fn = start_process_on; 429 else 430 start_fn = start_thread_on; 431 432 if (((argc - optind) != 2)) { 433 cpu1 = cpu2 = pick_online_cpu(); 434 } else { 435 cpu1 = atoi(argv[optind++]); 436 cpu2 = atoi(argv[optind++]); 437 } 438 439 printf("Using %s with ", processes ? "processes" : "threads"); 440 441 if (actions == &pipe_actions) 442 printf("pipe"); 443 else if (actions == &yield_actions) 444 printf("yield"); 445 else 446 printf("futex"); 447 448 printf(" on cpus %d/%d touching FP:%s altivec:%s vector:%s vdso:%s\n", 449 cpu1, cpu2, touch_fp ? "yes" : "no", touch_altivec ? "yes" : "no", 450 touch_vector ? "yes" : "no", touch_vdso ? "yes" : "no"); 451 452 /* Create a new process group so we can signal everyone for exit */ 453 setpgid(getpid(), getpid()); 454 455 signal(SIGUSR1, sigusr1_handler); 456 457 actions->setup(cpu1, cpu2); 458 459 start_fn(actions->thread1, NULL, cpu1); 460 start_fn(actions->thread2, NULL, cpu2); 461 462 while (1) 463 sleep(3600); 464 465 return 0; 466 } 467