/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * This provides a handful of correctness and speed tests on our atomic * operations. * * This doesn't really belong here, but we currently lack a better place * for it, so this will do for now. */ #include "Dalvik.h" #include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <cutils/atomic.h> #ifdef __arm__ # include <machine/cpu-features.h> #endif #define USE_ATOMIC 1 #define THREAD_COUNT 10 #define ITERATION_COUNT 500000 #ifdef HAVE_ANDROID_OS /*#define TEST_BIONIC 1*/ #endif #ifdef TEST_BIONIC extern int __atomic_cmpxchg(int old, int _new, volatile int *ptr); extern int __atomic_swap(int _new, volatile int *ptr); extern int __atomic_dec(volatile int *ptr); extern int __atomic_inc(volatile int *ptr); #endif static pthread_mutex_t waitLock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t waitCond = PTHREAD_COND_INITIALIZER; static volatile int threadsStarted = 0; /* results */ static int incTest = 0; static int decTest = 0; static int addTest = 0; static int andTest = 0; static int orTest = 0; static int casTest = 0; static int failingCasTest = 0; static int swapTest = 0; static int64_t wideCasTest = 0x6600000077000000LL; /* * Get a relative time value. */ static int64_t getRelativeTimeNsec(void) { #define HAVE_POSIX_CLOCKS #ifdef HAVE_POSIX_CLOCKS struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (int64_t) now.tv_sec*1000000000LL + now.tv_nsec; #else struct timeval now; gettimeofday(&now, NULL); return (int64_t) now.tv_sec*1000000000LL + now.tv_usec * 1000LL; #endif } /* * Non-atomic implementations, for comparison. * * If these get inlined the compiler may figure out what we're up to and * completely elide the operations. */ static void incr(void) __attribute__((noinline)); static void decr(void) __attribute__((noinline)); static void add(int addVal) __attribute__((noinline)); static int compareAndSwap(int oldVal, int newVal, int* addr) __attribute__((noinline)); static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr) __attribute__((noinline)); static void incr(void) { incTest++; } static void decr(void) { decTest--; } static void add(int32_t addVal) { addTest += addVal; } static int compareAndSwap(int32_t oldVal, int32_t newVal, int32_t* addr) { if (*addr == oldVal) { *addr = newVal; return 0; } return 1; } static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr) { if (*addr == oldVal) { *addr = newVal; return 0; } return 1; } /* * Exercise several of the atomic ops. */ static void doAtomicTest(int num) { int addVal = (num & 0x01) + 1; int i; for (i = 0; i < ITERATION_COUNT; i++) { if (USE_ATOMIC) { android_atomic_inc(&incTest); android_atomic_dec(&decTest); android_atomic_add(addVal, &addTest); int val; do { val = casTest; } while (android_atomic_release_cas(val, val+3, &casTest) != 0); do { val = casTest; } while (android_atomic_acquire_cas(val, val-1, &casTest) != 0); int64_t wval; do { wval = dvmQuasiAtomicRead64(&wideCasTest); } while (dvmQuasiAtomicCas64(wval, wval + 0x0000002000000001LL, &wideCasTest) != 0); do { wval = dvmQuasiAtomicRead64(&wideCasTest); } while (dvmQuasiAtomicCas64(wval, wval - 0x0000002000000001LL, &wideCasTest) != 0); } else { incr(); decr(); add(addVal); int val; do { val = casTest; } while (compareAndSwap(val, val+3, &casTest) != 0); do { val = casTest; } while (compareAndSwap(val, val-1, &casTest) != 0); int64_t wval; do { wval = wideCasTest; } while (compareAndSwapWide(wval, wval + 0x0000002000000001LL, &wideCasTest) != 0); do { wval = wideCasTest; } while (compareAndSwapWide(wval, wval - 0x0000002000000001LL, &wideCasTest) != 0); } } } /* * Entry point for multi-thread test. */ static void* atomicTest(void* arg) { pthread_mutex_lock(&waitLock); threadsStarted++; pthread_cond_wait(&waitCond, &waitLock); pthread_mutex_unlock(&waitLock); doAtomicTest((int) arg); return NULL; } /* lifted from a VM test */ static int64_t testAtomicSpeedSub(int repeatCount) { static int value = 7; int* valuePtr = &value; int64_t start, end; int i; start = getRelativeTimeNsec(); for (i = repeatCount / 10; i != 0; i--) { if (USE_ATOMIC) { // succeed 10x (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); (void) android_atomic_release_cas(7, 7, valuePtr); } else { // succeed 10x compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); compareAndSwap(7, 7, valuePtr); } } end = getRelativeTimeNsec(); dvmFprintf(stdout, "."); fflush(stdout); return end - start; } static void testAtomicSpeed(void) { static const int kIterations = 10; static const int kRepeatCount = 5 * 1000 * 1000; static const int kDelay = 50 * 1000; int64_t results[kIterations]; int i; for (i = 0; i < kIterations; i++) { results[i] = testAtomicSpeedSub(kRepeatCount); usleep(kDelay); } dvmFprintf(stdout, "\n"); dvmFprintf(stdout, "%s speed test results (%d per iteration):\n", USE_ATOMIC ? "Atomic" : "Non-atomic", kRepeatCount); for (i = 0; i < kIterations; i++) { dvmFprintf(stdout, " %2d: %.3fns\n", i, (double) results[i] / kRepeatCount); } } /* * Start tests, show results. */ bool dvmTestAtomicSpeed(void) { pthread_t threads[THREAD_COUNT]; void *(*startRoutine)(void*) = atomicTest; int64_t startWhen, endWhen; #if defined(__ARM_ARCH__) dvmFprintf(stdout, "__ARM_ARCH__ is %d\n", __ARM_ARCH__); #endif #if defined(ANDROID_SMP) dvmFprintf(stdout, "ANDROID_SMP is %d\n", ANDROID_SMP); #endif dvmFprintf(stdout, "Creating threads\n"); int i; for (i = 0; i < THREAD_COUNT; i++) { void* arg = (void*) i; if (pthread_create(&threads[i], NULL, startRoutine, arg) != 0) { dvmFprintf(stderr, "thread create failed\n"); } } /* wait for all the threads to reach the starting line */ while (1) { pthread_mutex_lock(&waitLock); if (threadsStarted == THREAD_COUNT) { dvmFprintf(stdout, "Starting test\n"); startWhen = getRelativeTimeNsec(); pthread_cond_broadcast(&waitCond); pthread_mutex_unlock(&waitLock); break; } pthread_mutex_unlock(&waitLock); usleep(100000); } for (i = 0; i < THREAD_COUNT; i++) { void* retval; if (pthread_join(threads[i], &retval) != 0) { dvmFprintf(stderr, "thread join (%d) failed\n", i); } } endWhen = getRelativeTimeNsec(); dvmFprintf(stdout, "All threads stopped, time is %.6fms\n", (endWhen - startWhen) / 1000000.0); /* * Show results; expecting: * * incTest = 5000000 * decTest = -5000000 * addTest = 7500000 * casTest = 10000000 * wideCasTest = 0x6600000077000000 */ dvmFprintf(stdout, "incTest = %d\n", incTest); dvmFprintf(stdout, "decTest = %d\n", decTest); dvmFprintf(stdout, "addTest = %d\n", addTest); dvmFprintf(stdout, "casTest = %d\n", casTest); dvmFprintf(stdout, "wideCasTest = 0x%llx\n", wideCasTest); /* do again, serially (SMP check) */ startWhen = getRelativeTimeNsec(); for (i = 0; i < THREAD_COUNT; i++) { doAtomicTest(i); } endWhen = getRelativeTimeNsec(); dvmFprintf(stdout, "Same iterations done serially: time is %.6fms\n", (endWhen - startWhen) / 1000000.0); /* * Hard to do a meaningful thrash test on these, so just do a simple * function test. */ andTest = 0xffd7fa96; orTest = 0x122221ff; swapTest = 0x11111111; android_atomic_and(0xfffdaf96, &andTest); android_atomic_or(0xdeaaeb00, &orTest); int oldSwap = android_atomic_swap(0x22222222, &swapTest); int oldSwap2 = android_atomic_swap(0x33333333, &swapTest); if (android_atomic_release_cas(failingCasTest+1, failingCasTest-1, &failingCasTest) == 0) dvmFprintf(stdout, "failing test did not fail!\n"); dvmFprintf(stdout, "andTest = 0x%x\n", andTest); dvmFprintf(stdout, "orTest = 0x%x\n", orTest); dvmFprintf(stdout, "swapTest = 0x%x -> 0x%x\n", oldSwap, oldSwap2); dvmFprintf(stdout, "swapTest = 0x%x -> 0x%x\n", oldSwap2, swapTest); dvmFprintf(stdout, "failingCasTest = %d\n", failingCasTest); #ifdef TEST_BIONIC /* * Quick function test on the bionic ops. */ int prev; int tester = 7; prev = __atomic_inc(&tester); __atomic_inc(&tester); __atomic_inc(&tester); dvmFprintf(stdout, "bionic 3 inc: %d -> %d\n", prev, tester); prev = __atomic_dec(&tester); __atomic_dec(&tester); __atomic_dec(&tester); dvmFprintf(stdout, "bionic 3 dec: %d -> %d\n", prev, tester); prev = __atomic_swap(27, &tester); dvmFprintf(stdout, "bionic swap: %d -> %d\n", prev, tester); int swapok = __atomic_cmpxchg(27, 72, &tester); dvmFprintf(stdout, "bionic cmpxchg: %d (%d)\n", tester, swapok); #endif testAtomicSpeed(); return 0; }