summaryrefslogtreecommitdiffstats
path: root/storage/tokudb/PerconaFT/util/tests/test-rwlock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/tokudb/PerconaFT/util/tests/test-rwlock.cc')
-rw-r--r--storage/tokudb/PerconaFT/util/tests/test-rwlock.cc403
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;
+}
+