diff options
Diffstat (limited to 'storage/tokudb/PerconaFT/util/tests/test-rwlock.cc')
-rw-r--r-- | storage/tokudb/PerconaFT/util/tests/test-rwlock.cc | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/storage/tokudb/PerconaFT/util/tests/test-rwlock.cc b/storage/tokudb/PerconaFT/util/tests/test-rwlock.cc new file mode 100644 index 00000000..56dd3f6b --- /dev/null +++ b/storage/tokudb/PerconaFT/util/tests/test-rwlock.cc @@ -0,0 +1,403 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4: +#ident "$Id$" +/*====== +This file is part of PerconaFT. + + +Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved. + + PerconaFT is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2, + as published by the Free Software Foundation. + + PerconaFT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PerconaFT. If not, see <http://www.gnu.org/licenses/>. + +---------------------------------------- + + PerconaFT is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License, version 3, + as published by the Free Software Foundation. + + PerconaFT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with PerconaFT. If not, see <http://www.gnu.org/licenses/>. +======= */ + +#ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved." + +// Here are some timing numbers: +// (Note: The not-quite-working version with cas can be found in r22519 of https://svn.tokutek.com/tokudb/toku/tokudb.2825/) It's about as fast as "Best cas".) +// +// On ramie (2.53GHz E5540) +// Best nop time= 1.074300ns +// Best cas time= 8.595600ns +// Best mutex time= 19.340201ns +// Best rwlock time= 34.024799ns +// Best util rwlock time= 38.680500ns +// Best prelocked time= 2.148700ns +// Best fair rwlock time= 45.127600ns +// On laptop +// Best nop time= 2.876000ns +// Best cas time= 15.362500ns +// Best mutex time= 51.951498ns +// Best rwlock time= 97.721201ns +// Best util rwlock time=110.456800ns +// Best prelocked time= 4.240100ns +// Best fair rwlock time=113.119102ns +// +// Analysis: If the mutex can be prelocked (as cachetable does, it uses the same mutex for the cachetable and for the condition variable protecting the cache table) +// then you can save quite a bit. What does the cachetable do? +// During pin: (In the common case:) It grabs the mutex, grabs a read lock, and releases the mutex. +// During unpin: It grabs the mutex, unlocks the rwlock lock in the pair, and releases the mutex. +// Both actions must acquire a cachetable lock during that time, so definitely saves time to do it that way. + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <toku_portability.h> +#include <toku_assert.h> +#include <portability/toku_atomic.h> +#include <portability/toku_pthread.h> +#include <portability/toku_time.h> +#include <util/frwlock.h> +#include <util/rwlock.h> +#include "rwlock_condvar.h" + +static int verbose=1; +static int timing_only=0; + +static void parse_args (int argc, const char *argv[]) { + const char *progname = argv[0]; + argc--; argv++; + while (argc>0) { + if (strcmp(argv[0], "-v")==0) { + verbose++; + } else if (strcmp(argv[0], "-q")==0) { + verbose--; + } else if (strcmp(argv[0], "--timing-only")==0) { + timing_only=1; + } else { + fprintf(stderr, "Usage: %s {-q}* {-v}* {--timing-only}\n", progname); + exit(1); + } + argc--; argv++; + } +} + +static const int T=6; +static const int N=10000000; + +static double best_nop_time=1e12; +static double best_fcall_time=1e12; +static double best_cas_time=1e12; +static double best_mutex_time=1e12; +static double best_rwlock_time=1e12; +static double best_util_time=1e12; +static double best_prelocked_time=1e12; +static double best_frwlock_time=1e12; +static double best_frwlock_prelocked_time=1e12; +static double mind(double a, double b) { if (a<b) return a; else return b; } + +#if 0 +// gcc 4.4.4 (fedora 12) doesn't introduce memory barriers on these writes, so I think that volatile is not enough for sequential consistency. +// Intel guarantees that writes are seen in the same order as they were performed on one processor. But if there were two processors, funny things could happen. +volatile int sc_a, sc_b; +void sequential_consistency (void) { + sc_a = 1; + sc_b = 0; +} +#endif + +// Declaring val to be volatile produces essentially identical code as putting the asm volatile memory statements in. +// gcc is not introducing memory barriers to force sequential consistency on volatile memory writes. +// That's probably good enough for us, since we'll have a barrier instruction anywhere it matters. +volatile int val = 0; + +static void time_nop (void) __attribute((__noinline__)); // don't want it inline, because it messes up timing. +static void time_nop (void) { + struct timeval start,end; + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + if (val!=0) abort(); + val=1; + //__asm__ volatile ("" : : : "memory"); + val=0; + //__asm__ volatile ("" : : : "memory"); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "nop = %.6fns/(lock+unlock)\n", diff); + best_nop_time=mind(best_nop_time,diff); + } +} + +// This function is defined so we can measure the cost of a function call. +int fcall_nop (int i) __attribute__((__noinline__)); +int fcall_nop (int i) { + return i; +} + +void time_fcall (void) __attribute((__noinline__)); +void time_fcall (void) { + struct timeval start,end; + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + fcall_nop(i); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "fcall = %.6fns/(lock+unlock)\n", diff); + best_fcall_time=mind(best_fcall_time,diff); + } +} + +void time_cas (void) __attribute__((__noinline__)); +void time_cas (void) { + volatile int64_t tval = 0; + struct timeval start,end; + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + { int r = toku_sync_val_compare_and_swap(&tval, 0, 1); assert(r==0); } + { int r = toku_sync_val_compare_and_swap(&tval, 1, 0); assert(r==1); } + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "cas = %.6fns/(lock+unlock)\n", diff); + best_cas_time=mind(best_cas_time,diff); + } +} + + +void time_pthread_mutex (void) __attribute__((__noinline__)); +void time_pthread_mutex (void) { + pthread_mutex_t mutex; + { int r = pthread_mutex_init(&mutex, NULL); assert(r==0); } + struct timeval start,end; + pthread_mutex_lock(&mutex); + pthread_mutex_unlock(&mutex); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + pthread_mutex_lock(&mutex); + pthread_mutex_unlock(&mutex); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "pthread_mutex = %.6fns/(lock+unlock)\n", diff); + best_mutex_time=mind(best_mutex_time,diff); + } + { int r = pthread_mutex_destroy(&mutex); assert(r==0); } +} + +void time_pthread_rwlock (void) __attribute__((__noinline__)); +void time_pthread_rwlock (void) { + pthread_rwlock_t mutex; + { int r = pthread_rwlock_init(&mutex, NULL); assert(r==0); } + struct timeval start,end; + pthread_rwlock_rdlock(&mutex); + pthread_rwlock_unlock(&mutex); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + pthread_rwlock_rdlock(&mutex); + pthread_rwlock_unlock(&mutex); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "pthread_rwlock(r) = %.6fns/(lock+unlock)\n", diff); + best_rwlock_time=mind(best_rwlock_time,diff); + } + { int r = pthread_rwlock_destroy(&mutex); assert(r==0); } +} + +static void util_rwlock_lock (RWLOCK rwlock, toku_mutex_t *mutex) { + toku_mutex_lock(mutex); + rwlock_read_lock(rwlock, mutex); + toku_mutex_unlock(mutex); +} + +static void util_rwlock_unlock (RWLOCK rwlock, toku_mutex_t *mutex) { + toku_mutex_lock(mutex); + rwlock_read_unlock(rwlock); + toku_mutex_unlock(mutex); +} + +// Time the read lock that's in util/rwlock.h +void time_util_rwlock(void) __attribute((__noinline__)); +void time_util_rwlock(void) { + struct st_rwlock rwlock; + toku_mutex_t external_mutex; + toku_mutex_init(toku_uninstrumented, &external_mutex, nullptr); + rwlock_init(toku_uninstrumented, &rwlock); + struct timeval start, end; + + util_rwlock_lock(&rwlock, &external_mutex); + util_rwlock_unlock(&rwlock, &external_mutex); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + util_rwlock_lock(&rwlock, &external_mutex); + util_rwlock_unlock(&rwlock, &external_mutex); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "util_rwlock(r) = %.6fns/(lock+unlock)\n", diff); + best_util_time=mind(best_util_time,diff); + } + rwlock_destroy(&rwlock); + toku_mutex_destroy(&external_mutex); +} + +// Time the read lock that's in util/rwlock.h, assuming the mutex is already +// held. +void time_util_prelocked_rwlock(void) __attribute__((__noinline__)); +void time_util_prelocked_rwlock(void) { + struct st_rwlock rwlock; + toku_mutex_t external_mutex; + toku_mutex_init(toku_uninstrumented, &external_mutex, nullptr); + toku_mutex_lock(&external_mutex); + rwlock_init(toku_uninstrumented, &rwlock); + struct timeval start, end; + + rwlock_read_lock(&rwlock, &external_mutex); + rwlock_read_unlock(&rwlock); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + rwlock_read_lock(&rwlock, &external_mutex); + rwlock_read_unlock(&rwlock); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "pre_util_rwlock(r) = %.6fns/(lock+unlock)\n", diff); + best_prelocked_time=mind(best_prelocked_time,diff); + } + rwlock_destroy(&rwlock); + toku_mutex_unlock(&external_mutex); + toku_mutex_destroy(&external_mutex); +} + +void time_frwlock_prelocked(void) __attribute__((__noinline__)); +void time_frwlock_prelocked(void) { + toku_mutex_t external_mutex; + toku_mutex_init(toku_uninstrumented, &external_mutex, nullptr); + struct timeval start, end; + toku::frwlock x; + x.init(&external_mutex); + toku_mutex_lock(&external_mutex); + bool got_lock; + x.read_lock(); + x.read_unlock(); + + got_lock = x.try_read_lock(); + invariant(got_lock); + x.read_unlock(); + x.write_lock(true); + x.write_unlock(); + got_lock = x.try_write_lock(true); + invariant(got_lock); + x.write_unlock(); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + x.read_lock(); + x.read_unlock(); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "frwlock_prelocked = %.6fns/(lock+unlock)\n", diff); + best_frwlock_prelocked_time=mind(best_frwlock_prelocked_time,diff); + } + x.deinit(); + toku_mutex_unlock(&external_mutex); + toku_mutex_destroy(&external_mutex); +} + +void time_frwlock(void) __attribute__((__noinline__)); +void time_frwlock(void) { + toku_mutex_t external_mutex; + toku_mutex_init(toku_uninstrumented, &external_mutex, nullptr); + struct timeval start, end; + toku::frwlock x; + x.init(&external_mutex); + toku_mutex_lock(&external_mutex); + x.read_lock(); + x.read_unlock(); + toku_mutex_unlock(&external_mutex); + for (int t=0; t<T; t++) { + gettimeofday(&start, NULL); + for (int i=0; i<N; i++) { + toku_mutex_lock(&external_mutex); + x.read_lock(); + toku_mutex_unlock(&external_mutex); + + toku_mutex_lock(&external_mutex); + x.read_unlock(); + toku_mutex_unlock(&external_mutex); + } + gettimeofday(&end, NULL); + double diff = 1e9*toku_tdiff(&end, &start)/N; + if (verbose>1) + fprintf(stderr, "frwlock = %.6fns/(lock+unlock)\n", diff); + best_frwlock_time=mind(best_frwlock_time,diff); + } + x.deinit(); + toku_mutex_destroy(&external_mutex); +} + +int main (int argc, const char *argv[]) { + parse_args(argc, argv); + if (timing_only) { + if (1) { // to make it easy to only time the templated frwlock + time_nop(); + time_fcall(); + time_cas(); + time_pthread_mutex(); + time_pthread_rwlock(); + time_util_rwlock(); + time_util_prelocked_rwlock(); + } + time_frwlock(); + time_frwlock_prelocked(); + if (verbose>0) { + if (1) { // to make it easy to only time the templated frwlock + printf("// Best nop time=%10.6fns\n", best_nop_time); + printf("// Best fcall time=%10.6fns\n", best_fcall_time); + printf("// Best cas time=%10.6fns\n", best_cas_time); + printf("// Best mutex time=%10.6fns\n", best_mutex_time); + printf("// Best rwlock time=%10.6fns\n", best_rwlock_time); + printf("// Best util rwlock time=%10.6fns\n", best_util_time); + printf("// Best prelocked time=%10.6fns\n", best_prelocked_time); + } + printf("// Best frwlock time=%10.6fns\n", best_frwlock_time); + printf("// Best frwlock_pre time=%10.6fns\n", best_frwlock_prelocked_time); + } + } + return 0; +} + |