/*
* 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;
}