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