diff options
author | David Leonard <d@cvs.openbsd.org> | 1999-01-17 23:40:03 +0000 |
---|---|---|
committer | David Leonard <d@cvs.openbsd.org> | 1999-01-17 23:40:03 +0000 |
commit | 88445003a8b78125cabf9aafcd7b49f1af9a9f87 (patch) | |
tree | bcf1bc7e71f04e89a9036d7795a2a0c3422ae7f8 | |
parent | 07aad870427e09b1a5320970648cd235915fa9b2 (diff) |
some performance metrics
-rw-r--r-- | lib/libc_r/BENCH/Makefile | 16 | ||||
-rw-r--r-- | lib/libc_r/BENCH/bench.h | 65 | ||||
-rw-r--r-- | lib/libc_r/BENCH/cond_nowait.c | 35 | ||||
-rw-r--r-- | lib/libc_r/BENCH/cond_timed.c | 78 | ||||
-rw-r--r-- | lib/libc_r/BENCH/cond_wake.c | 58 | ||||
-rw-r--r-- | lib/libc_r/BENCH/mutex_cont.c | 128 | ||||
-rw-r--r-- | lib/libc_r/BENCH/mutex_nocont.c | 29 | ||||
-rw-r--r-- | lib/libc_r/BENCH/null.c | 26 | ||||
-rw-r--r-- | lib/libc_r/BENCH/once_overhead.c | 30 | ||||
-rw-r--r-- | lib/libc_r/BENCH/self_overhead.c | 79 | ||||
-rw-r--r-- | lib/libc_r/BENCH/yield.c | 55 |
11 files changed, 599 insertions, 0 deletions
diff --git a/lib/libc_r/BENCH/Makefile b/lib/libc_r/BENCH/Makefile new file mode 100644 index 00000000000..22a9618d291 --- /dev/null +++ b/lib/libc_r/BENCH/Makefile @@ -0,0 +1,16 @@ + +CPPFLAGS += -pthread +LDFLAGS += -pthread +CFLAGS += -O -ggdb +METRICS = null once_overhead self_overhead mutex_nocont mutex_cont\ + cond_nowait cond_wake cond_timed +MKDEP = -p +SRCS = ${METRICS:=.c} +CLEANFILES += ${METRICS} + +all: ${METRICS} + @${SUDO} nice -n -19 ${SHELL} -c \ + 'ulimit -d 65536; for m in ${METRICS}; do $$m || exit; done' + + +.include <bsd.prog.mk> diff --git a/lib/libc_r/BENCH/bench.h b/lib/libc_r/BENCH/bench.h new file mode 100644 index 00000000000..1d68915a75e --- /dev/null +++ b/lib/libc_r/BENCH/bench.h @@ -0,0 +1,65 @@ + +#define BENCH_LOOPS (16384) +#include <sys/time.h> + +typedef struct { + int i; + int n; + int divisor; + struct timespec start; + struct timespec end; + struct timespec elapsed; + double average; + char *name; + char *doc; + char *units; +} bench_t; + +#define bench_now(tsp) \ + clock_gettime(CLOCK_REALTIME, (tsp)) + +/* + * Repeat the body of the loop 'max' times, with a few extra 'warm up' + * cycles to negate cache effects. + */ +#define bench_amortize(b, max) \ + for ((b)->i = -64, \ + (b)->n = (max); \ + (b)->i < (b)->n; \ + (b)->i ++, \ + ((b)->i == 0 ? bench_now(&(b)->start) : \ + ((b)->i == (b)->n ? bench_now(&(b)->end) \ + :0))\ + ) + +#define bench_init(b, nm, dc, un) do { \ + (b)->name = (nm); \ + (b)->doc = (dc); \ + (b)->units = (un); \ + timespecclear(&(b)->start); \ + timespecclear(&(b)->end); \ + timespecclear(&(b)->elapsed); \ + (b)->n = (b)->i = 0; \ + (b)->divisor = 1; \ +} while (0) + +#define bench_header(b) \ + printf("----------------------------------------------------\n" \ + "Name:\t%s\nDesc:%s\n", (b)->name, (b)->doc) + +#define bench_report(b) do { \ + struct timespec elapsed; \ + bench_t overhead; \ + \ + /* compute the loop overhead */ \ + bench_amortize(&overhead, BENCH_LOOPS) { /* nothing */ } \ + \ + timespecsub(&(b)->end, &(b)->start, &(b)->elapsed); \ + (b)->average = ((double)(b)->elapsed.tv_sec * 1000000000.0 + \ + (b)->elapsed.tv_nsec) / (double)((b)->divisor) / \ + (double)((b)->n); \ + \ + printf("Time: %f usec %s\n", (b)->average, (b)->units); \ + if ((b)->divisor != 1) \ + printf("\t(%d operations per cycle)\n", (b)->divisor); \ +} while (0) diff --git a/lib/libc_r/BENCH/cond_nowait.c b/lib/libc_r/BENCH/cond_nowait.c new file mode 100644 index 00000000000..88c1769c502 --- /dev/null +++ b/lib/libc_r/BENCH/cond_nowait.c @@ -0,0 +1,35 @@ +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Condition Variable Signal/Broadcast, No Waiters"; +static char doc[] = +"\tThis is the amount of time needed to execute pthread_cond_signal()\n" +"\tor pthread_cond_broadcast() if there are no threads blocked on\n" +"\tthe condition."; + +int +main() { + pthread_cond_t c; + bench_t b; + + bench_init(&b, name, doc, "per call of pthread_cond_signal()"); + bench_header(&b); + pthread_cond_init(&c, NULL); + bench_amortize(&b, BENCH_LOOPS) { + pthread_cond_signal(&c); + } + bench_report(&b); + + bench_init(&b, NULL, NULL, "per call of pthread_cond_broadcast()"); + pthread_cond_init(&c, NULL); + bench_amortize(&b, BENCH_LOOPS) { + pthread_cond_broadcast(&c); + } + bench_report(&b); + + exit(0); +} + + diff --git a/lib/libc_r/BENCH/cond_timed.c b/lib/libc_r/BENCH/cond_timed.c new file mode 100644 index 00000000000..dc48cd7e340 --- /dev/null +++ b/lib/libc_r/BENCH/cond_timed.c @@ -0,0 +1,78 @@ +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Time of Wakeup After Timed Wait"; +static char doc[] = +"\tThe tiem required for the highest-priority thread to rresume\n" +"\texecution after a call to pthread_cond_timedwait(). Metrics\n" +"\tare provided for both the cases when the pthread_cond_timedwait()\n" +"\tcall is awakened by a call to pthread_cond_signal() and when\n" +"\tthe absolute time to be awaited has already passed at the time\n" +"\tof the call."; + +pthread_mutex_t m1, m2; +pthread_cond_t c; +bench_t b; +struct timespec waketime; + +void * +other_thread(arg) + void *arg; +{ + + pthread_set_name_np(pthread_self(), "oth"); + pthread_mutex_lock(&m2); + + bench_amortize(&b, BENCH_LOOPS) { + pthread_cond_timedwait(&c, &m2, &waketime); + pthread_cond_signal(&c); + } + pthread_mutex_unlock(&m2); +} + +int +main() { + pthread_t other; + pthread_mutex_t m; + struct timespec ts; + + bench_init(&b, name, doc, "from signal to wait inclusive"); + b.n = BENCH_LOOPS; + bench_header(&b); + pthread_cond_init(&c, NULL); + pthread_mutex_init(&m1, NULL); + pthread_mutex_init(&m2, NULL); + + clock_gettime(CLOCK_REALTIME, &waketime); + waketime.tv_sec += 10000; /* shouldn't take this long! */ + pthread_mutex_lock(&m1); + + pthread_create(&other, NULL, other_thread, NULL); + pthread_yield(); + while (b.i < b.n) { + pthread_cond_signal(&c); + pthread_cond_timedwait(&c, &m1, &waketime); + } + pthread_join(other, NULL); + pthread_mutex_unlock(&m1); + + b.divisor = 2; + bench_report(&b); + + /* expired test */ + bench_init(&b, NULL, NULL, "per call when already expired"); + pthread_mutex_init(&m, NULL); + pthread_mutex_lock(&m); + timespecclear(&ts); + bench_amortize(&b, BENCH_LOOPS) { + pthread_cond_timedwait(&c, &m, &ts); + } + pthread_mutex_unlock(&m); + bench_report(&b); + + exit(0); +} + + diff --git a/lib/libc_r/BENCH/cond_wake.c b/lib/libc_r/BENCH/cond_wake.c new file mode 100644 index 00000000000..11055baaeed --- /dev/null +++ b/lib/libc_r/BENCH/cond_wake.c @@ -0,0 +1,58 @@ +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Condition Variable, Wake Up"; +static char doc[] = +"\tThis is the amount of time from when one thread calls\n" +"\tpthread_cond_signal() and a thread blocked on that condition\n" +"\tvariable returns from its pthread_cond_wait() call. The condition\n" +"\tand its associated mutex should not be used by any other thread.\n" +"\tMetrics shall be provided for both the case when the\n" +"\tpthread_cond_signal() call is executed under the associated mutex,\n" +"\tas well as not under the mutex."; + +pthread_mutex_t m1, m2; +pthread_cond_t c; +bench_t b; + +void * +other_thread(arg) + void *arg; +{ + + pthread_set_name_np(pthread_self(), "oth"); + pthread_mutex_lock(&m2); + + bench_amortize(&b, BENCH_LOOPS) { + pthread_cond_wait(&c, &m2); + pthread_cond_signal(&c); + } + pthread_mutex_unlock(&m2); +} + +int +main() { + pthread_t other; + bench_init(&b, name, doc, "per call"); + b.n = BENCH_LOOPS; + bench_header(&b); + pthread_cond_init(&c, NULL); + pthread_mutex_init(&m1, NULL); + pthread_mutex_init(&m2, NULL); + pthread_mutex_lock(&m1); + pthread_create(&other, NULL, other_thread, NULL); + + pthread_yield(); + while (b.i < b.n) { + pthread_cond_signal(&c); + pthread_cond_wait(&c, &m1); + } + + b.divisor = 2; + bench_report(&b); + exit(0); +} + + diff --git a/lib/libc_r/BENCH/mutex_cont.c b/lib/libc_r/BENCH/mutex_cont.c new file mode 100644 index 00000000000..de9eecb0bce --- /dev/null +++ b/lib/libc_r/BENCH/mutex_cont.c @@ -0,0 +1,128 @@ +#include <stdio.h> +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Mutex Lock/Unlock, Contention"; +static char doc[] = +"\tThis is the time interval between when one thread calls\n" +"\tpthread_mutex_unlock() and another thread that was blocked\n" +"\ton pthread_mutex_lock() returns with the lock held."; + +/* + +The order of locking looks like this: + + A B 1 2 3 + =============== =============== == == == + lock(2) A + yield() A + lock(1) A B + lock(3) B A B + yield() B A B + lock(1) Ba A B +------- + unlock(1) a A B + lock(2) a Ab B + ^ A Ab B + unlock(2) A b B + lock(3) A b Ba + ^ A B Ba + unlock(3) A B a + lock(1) Ab B a + ^ Ab B A + unlock(1) b B A + lock(2) b Ba A + ^ B Ba A + unlock(2) B a A + lock(3) B a Ab + ^ B A Ab + unlock(3) B A b + lock(1) Ba A b + ^ Ba A B +------- + unlock(1) a A B + unlock(3) a A + exit + ^ A A + unlock(1) A + unlock(2) + exit + + In every cycle, there will be 6 transitions and 6 lock/unlock + pairs. So, to compute the transition time, we subtract the + lock/unlock time computed without contention. +*/ + +static pthread_mutex_t m1, m2, m3; +static bench_t ba, bb; + +void * +thread_a(arg) +{ + pthread_set_name_np(pthread_self(), "ta"); + pthread_mutex_lock(&m2); + pthread_yield(); + + pthread_mutex_lock(&m1); + bench_amortize(&ba, BENCH_LOOPS) { + pthread_mutex_unlock(&m2); + pthread_mutex_lock(&m3); + pthread_mutex_unlock(&m1); + pthread_mutex_lock(&m2); + pthread_mutex_unlock(&m3); + pthread_mutex_lock(&m1); + } + pthread_mutex_unlock(&m1); + pthread_mutex_unlock(&m2); + return (NULL); +} + +void * +thread_b(arg) +{ + pthread_set_name_np(pthread_self(), "tb"); + pthread_mutex_lock(&m1); + pthread_mutex_lock(&m3); + pthread_yield(); + + bench_amortize(&bb, BENCH_LOOPS) { + pthread_mutex_unlock(&m1); + pthread_mutex_lock(&m2); + pthread_mutex_unlock(&m3); + pthread_mutex_lock(&m1); + pthread_mutex_unlock(&m2); + pthread_mutex_lock(&m3); + } + pthread_mutex_unlock(&m1); + pthread_mutex_unlock(&m3); + return (NULL); +} + +int +main() { + pthread_t ta, tb; + + bench_init(&ba, name, doc, "from unlock to lock inclusive"); + bench_init(&bb, NULL, NULL, NULL); + + bench_header(&ba); + + pthread_mutex_init(&m1, NULL); + pthread_mutex_init(&m2, NULL); + pthread_mutex_init(&m3, NULL); + + pthread_create(&ta, NULL, thread_a, NULL); + pthread_create(&tb, NULL, thread_b, NULL); + + pthread_join(ta, NULL); + pthread_join(tb, NULL); + + ba.divisor = bb.divisor = 6; + + bench_report(&ba); +/* bench_report(&bb); */ + exit(0); +} + diff --git a/lib/libc_r/BENCH/mutex_nocont.c b/lib/libc_r/BENCH/mutex_nocont.c new file mode 100644 index 00000000000..46f13791296 --- /dev/null +++ b/lib/libc_r/BENCH/mutex_nocont.c @@ -0,0 +1,29 @@ +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Mutex Lock/Unlock, No Contention"; +static char doc[] = +"\tThis is the time interval needed to call pthread_mutex_lock()\n" +"\tfollowed immediately by pthread_mutex_unlock() on a mutex that\n" +"\tis unowned and which is only being used by the thread doing\n" +"\tthe test."; + +int +main() { + pthread_mutex_t m; + bench_t b; + + bench_init(&b, name, doc, "from lock to unlock inclusive"); + bench_header(&b); + pthread_mutex_init(&m, NULL); + bench_amortize(&b, BENCH_LOOPS) { + pthread_mutex_lock(&m); + pthread_mutex_unlock(&m); + } + bench_report(&b); + exit(0); +} + + diff --git a/lib/libc_r/BENCH/null.c b/lib/libc_r/BENCH/null.c new file mode 100644 index 00000000000..cbd7ad3a4f6 --- /dev/null +++ b/lib/libc_r/BENCH/null.c @@ -0,0 +1,26 @@ +#include <stdio.h> +#include <pthread.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Null test"; +static char doc[] = +"\tThe time needed for performing a tight empty loop. This indicates\n" +"\tthe overhead incurred by the measurement harness."; + + + +int +main() { + bench_t b; + + bench_init(&b, name, doc, "per cycle"); + bench_header(&b); + bench_amortize(&b, BENCH_LOOPS) { + /* nothng */ + } + bench_report(&b); + exit(0); +} + + diff --git a/lib/libc_r/BENCH/once_overhead.c b/lib/libc_r/BENCH/once_overhead.c new file mode 100644 index 00000000000..c353ffb8d52 --- /dev/null +++ b/lib/libc_r/BENCH/once_overhead.c @@ -0,0 +1,30 @@ + +#include <pthread.h> +#include "bench.h" + +static char name[] = "Once Overhead"; +static char doc[] = +"\tThe time needed for the highest priority thread to execute the\n" +"\tpthread_once() function when the init_routine has already been\n" +"\texecuted."; + +void +init_routine() +{ +} + +int +main() { + pthread_once_t once_control = PTHREAD_ONCE_INIT; + bench_t b; + bench_init(&b, name, doc, "per call"); + bench_header(&b); + pthread_once(&once_control, init_routine); + bench_amortize(&b, BENCH_LOOPS) { + pthread_once(&once_control, init_routine); + } + bench_report(&b); + exit(0); +} + + diff --git a/lib/libc_r/BENCH/self_overhead.c b/lib/libc_r/BENCH/self_overhead.c new file mode 100644 index 00000000000..eea74cfc75e --- /dev/null +++ b/lib/libc_r/BENCH/self_overhead.c @@ -0,0 +1,79 @@ +#include <pthread.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Self Overhead"; +static char doc[] = +"\tThe time needed for the highest priority thread to perform the\n" +"\tpthread_self() operation, for the following numbers of threads:\n" +"\t1, 21, 101, 1023"; + + +static int nthreads = 1; +pthread_t children[1024]; + +void * +child() { + pause(); +} + +void +numthreads(n) + int n; +{ + int error; + pthread_attr_t small_stack_attr; + + pthread_attr_init(&small_stack_attr); + pthread_attr_setstacksize(&small_stack_attr, PTHREAD_STACK_MIN); + + while (nthreads < n) { + error = pthread_create(&children[nthreads], + &small_stack_attr, child, NULL); + if (error != 0) + errx(1, "pthread_create #%d: %s", nthreads, + strerror(error)); + pthread_yield(); + nthreads++; + } + + while (nthreads > n) { + error = pthread_cancel(children[nthreads - 1]); + if (error != 0) + errx(1, "pthread_cancel: %s", strerror(error)); + pthread_yield(); + nthreads --; + } + + printf("\n#threads: %d\n", nthreads); +} + +void +doit(b, n) + bench_t *b; + int n; +{ + + numthreads(n); + bench_amortize(b, BENCH_LOOPS) { + pthread_self(); + } + bench_report(b); +} + +int +main() { + bench_t b; + + bench_init(&b, name, doc, "per call"); + bench_header(&b); + + doit(&b, 1); + doit(&b, 21); + doit(&b, 101); + doit(&b, 1023); + exit(0); +} + + diff --git a/lib/libc_r/BENCH/yield.c b/lib/libc_r/BENCH/yield.c new file mode 100644 index 00000000000..c197afd2b2d --- /dev/null +++ b/lib/libc_r/BENCH/yield.c @@ -0,0 +1,55 @@ +#include <pthread.h> +#include <sched.h> +#include <string.h> +#include <err.h> +#include "bench.h" + +static char name[] = "Thread Yield Time (Busy)"; +static char doc[] = +"\tThread yield time is defined as the amount of time between that\n" +"\tpoint when a running thread voluntarily gives up the CPU until\n" +"\tthe highest priority runnable thread begins execution of its\n" +"\tapplication code."; + +#ifdef DEBUG +volatile int state = 0; +#endif +bench_t b; + +void * +other_thread(arg) + void *arg; +{ + + pthread_set_name_np(pthread_self(), "oth"); + bench_amortize(&b, BENCH_LOOPS) { +#ifdef DEBUG + if (state != 0) abort(); + state = 1; +#endif + sched_yield(); + } +} + +int +main() { + pthread_t other; + + bench_init(&b, name, doc, "per yield"); + b.n = BENCH_LOOPS; + bench_header(&b); + pthread_create(&other, NULL, other_thread, NULL); + while (b.i < b.n) { +#ifdef DEBUG + if (state != 1) abort(); + state = 0; +#endif + sched_yield(); + } + pthread_join(other, NULL); + b.divisor = 2; + bench_report(&b); + exit(0); +} + + |