Home | History | Annotate | Download | only in test
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 /*
     18  * This provides a handful of correctness and speed tests on our atomic
     19  * operations.
     20  *
     21  * This doesn't really belong here, but we currently lack a better place
     22  * for it, so this will do for now.
     23  */
     24 #include "Dalvik.h"
     25 
     26 #include <stdlib.h>
     27 #include <stdio.h>
     28 #include <pthread.h>
     29 #include <unistd.h>
     30 #include <cutils/atomic.h>
     31 #ifdef __arm__
     32 # include <machine/cpu-features.h>
     33 #endif
     34 
     35 #define USE_ATOMIC      1
     36 #define THREAD_COUNT    10
     37 #define ITERATION_COUNT 500000
     38 
     39 #ifdef HAVE_ANDROID_OS
     40 /*#define TEST_BIONIC 1*/
     41 #endif
     42 
     43 
     44 #ifdef TEST_BIONIC
     45 extern int __atomic_cmpxchg(int old, int _new, volatile int *ptr);
     46 extern int __atomic_swap(int _new, volatile int *ptr);
     47 extern int __atomic_dec(volatile int *ptr);
     48 extern int __atomic_inc(volatile int *ptr);
     49 #endif
     50 
     51 static pthread_mutex_t waitLock = PTHREAD_MUTEX_INITIALIZER;
     52 static pthread_cond_t waitCond = PTHREAD_COND_INITIALIZER;
     53 
     54 static volatile int threadsStarted = 0;
     55 
     56 /* results */
     57 static int incTest = 0;
     58 static int decTest = 0;
     59 static int addTest = 0;
     60 static int andTest = 0;
     61 static int orTest = 0;
     62 static int casTest = 0;
     63 static int failingCasTest = 0;
     64 static int64_t wideCasTest = 0x6600000077000000LL;
     65 
     66 /*
     67  * Get a relative time value.
     68  */
     69 static int64_t getRelativeTimeNsec()
     70 {
     71 #define HAVE_POSIX_CLOCKS
     72 #ifdef HAVE_POSIX_CLOCKS
     73     struct timespec now;
     74     clock_gettime(CLOCK_MONOTONIC, &now);
     75     return (int64_t) now.tv_sec*1000000000LL + now.tv_nsec;
     76 #else
     77     struct timeval now;
     78     gettimeofday(&now, NULL);
     79     return (int64_t) now.tv_sec*1000000000LL + now.tv_usec * 1000LL;
     80 #endif
     81 }
     82 
     83 
     84 /*
     85  * Non-atomic implementations, for comparison.
     86  *
     87  * If these get inlined the compiler may figure out what we're up to and
     88  * completely elide the operations.
     89  */
     90 static void incr() __attribute__((noinline));
     91 static void decr() __attribute__((noinline));
     92 static void add(int addVal) __attribute__((noinline));
     93 static int compareAndSwap(int oldVal, int newVal, int* addr) __attribute__((noinline));
     94 static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr) __attribute__((noinline));
     95 
     96 static void incr()
     97 {
     98     incTest++;
     99 }
    100 static void decr()
    101 {
    102     decTest--;
    103 }
    104 static void add(int32_t addVal)
    105 {
    106     addTest += addVal;
    107 }
    108 static int compareAndSwap(int32_t oldVal, int32_t newVal, int32_t* addr)
    109 {
    110     if (*addr == oldVal) {
    111         *addr = newVal;
    112         return 0;
    113     }
    114     return 1;
    115 }
    116 static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr)
    117 {
    118     if (*addr == oldVal) {
    119         *addr = newVal;
    120         return 0;
    121     }
    122     return 1;
    123 }
    124 
    125 /*
    126  * Exercise several of the atomic ops.
    127  */
    128 static void doAtomicTest(int num)
    129 {
    130     int addVal = (num & 0x01) + 1;
    131 
    132     int i;
    133     for (i = 0; i < ITERATION_COUNT; i++) {
    134         if (USE_ATOMIC) {
    135             android_atomic_inc(&incTest);
    136             android_atomic_dec(&decTest);
    137             android_atomic_add(addVal, &addTest);
    138 
    139             int val;
    140             do {
    141                 val = casTest;
    142             } while (android_atomic_release_cas(val, val+3, &casTest) != 0);
    143             do {
    144                 val = casTest;
    145             } while (android_atomic_acquire_cas(val, val-1, &casTest) != 0);
    146 
    147             int64_t wval;
    148             do {
    149                 wval = dvmQuasiAtomicRead64(&wideCasTest);
    150             } while (dvmQuasiAtomicCas64(wval,
    151                         wval + 0x0000002000000001LL, &wideCasTest) != 0);
    152             do {
    153                 wval = dvmQuasiAtomicRead64(&wideCasTest);
    154             } while (dvmQuasiAtomicCas64(wval,
    155                         wval - 0x0000002000000001LL, &wideCasTest) != 0);
    156         } else {
    157             incr();
    158             decr();
    159             add(addVal);
    160 
    161             int val;
    162             do {
    163                 val = casTest;
    164             } while (compareAndSwap(val, val+3, &casTest) != 0);
    165             do {
    166                 val = casTest;
    167             } while (compareAndSwap(val, val-1, &casTest) != 0);
    168 
    169             int64_t wval;
    170             do {
    171                 wval = wideCasTest;
    172             } while (compareAndSwapWide(wval,
    173                         wval + 0x0000002000000001LL, &wideCasTest) != 0);
    174             do {
    175                 wval = wideCasTest;
    176             } while (compareAndSwapWide(wval,
    177                         wval - 0x0000002000000001LL, &wideCasTest) != 0);
    178         }
    179     }
    180 }
    181 
    182 /*
    183  * Entry point for multi-thread test.
    184  */
    185 static void* atomicTest(void* arg)
    186 {
    187     pthread_mutex_lock(&waitLock);
    188     threadsStarted++;
    189     pthread_cond_wait(&waitCond, &waitLock);
    190     pthread_mutex_unlock(&waitLock);
    191 
    192     doAtomicTest((int) arg);
    193 
    194     return NULL;
    195 }
    196 
    197 /* lifted from a VM test */
    198 static int64_t testAtomicSpeedSub(int repeatCount)
    199 {
    200     static int value = 7;
    201     int* valuePtr = &value;
    202     int64_t start, end;
    203     int i;
    204 
    205     start = getRelativeTimeNsec();
    206 
    207     for (i = repeatCount / 10; i != 0; i--) {
    208         if (USE_ATOMIC) {
    209             // succeed 10x
    210             android_atomic_release_cas(7, 7, valuePtr);
    211             android_atomic_release_cas(7, 7, valuePtr);
    212             android_atomic_release_cas(7, 7, valuePtr);
    213             android_atomic_release_cas(7, 7, valuePtr);
    214             android_atomic_release_cas(7, 7, valuePtr);
    215             android_atomic_release_cas(7, 7, valuePtr);
    216             android_atomic_release_cas(7, 7, valuePtr);
    217             android_atomic_release_cas(7, 7, valuePtr);
    218             android_atomic_release_cas(7, 7, valuePtr);
    219             android_atomic_release_cas(7, 7, valuePtr);
    220         } else {
    221             // succeed 10x
    222             compareAndSwap(7, 7, valuePtr);
    223             compareAndSwap(7, 7, valuePtr);
    224             compareAndSwap(7, 7, valuePtr);
    225             compareAndSwap(7, 7, valuePtr);
    226             compareAndSwap(7, 7, valuePtr);
    227             compareAndSwap(7, 7, valuePtr);
    228             compareAndSwap(7, 7, valuePtr);
    229             compareAndSwap(7, 7, valuePtr);
    230             compareAndSwap(7, 7, valuePtr);
    231             compareAndSwap(7, 7, valuePtr);
    232         }
    233     }
    234 
    235     end = getRelativeTimeNsec();
    236 
    237     dvmFprintf(stdout, ".");
    238     fflush(stdout);
    239     return end - start;
    240 }
    241 
    242 static void testAtomicSpeed()
    243 {
    244     static const int kIterations = 10;
    245     static const int kRepeatCount = 5 * 1000 * 1000;
    246     static const int kDelay = 50 * 1000;
    247     int64_t results[kIterations];
    248     int i;
    249 
    250     for (i = 0; i < kIterations; i++) {
    251         results[i] = testAtomicSpeedSub(kRepeatCount);
    252         usleep(kDelay);
    253     }
    254 
    255     dvmFprintf(stdout, "\n");
    256     dvmFprintf(stdout, "%s speed test results (%d per iteration):\n",
    257         USE_ATOMIC ? "Atomic" : "Non-atomic", kRepeatCount);
    258     for (i = 0; i < kIterations; i++) {
    259         dvmFprintf(stdout,
    260             " %2d: %.3fns\n", i, (double) results[i] / kRepeatCount);
    261     }
    262 }
    263 
    264 /*
    265  * Start tests, show results.
    266  */
    267 bool dvmTestAtomicSpeed()
    268 {
    269     pthread_t threads[THREAD_COUNT];
    270     void *(*startRoutine)(void*) = atomicTest;
    271     int64_t startWhen, endWhen;
    272 
    273 #if defined(__ARM_ARCH__)
    274     dvmFprintf(stdout, "__ARM_ARCH__ is %d\n", __ARM_ARCH__);
    275 #endif
    276 #if defined(ANDROID_SMP)
    277     dvmFprintf(stdout, "ANDROID_SMP is %d\n", ANDROID_SMP);
    278 #endif
    279     dvmFprintf(stdout, "Creating threads\n");
    280 
    281     int i;
    282     for (i = 0; i < THREAD_COUNT; i++) {
    283         void* arg = (void*) i;
    284         if (pthread_create(&threads[i], NULL, startRoutine, arg) != 0) {
    285             dvmFprintf(stderr, "thread create failed\n");
    286         }
    287     }
    288 
    289     /* wait for all the threads to reach the starting line */
    290     while (1) {
    291         pthread_mutex_lock(&waitLock);
    292         if (threadsStarted == THREAD_COUNT) {
    293             dvmFprintf(stdout, "Starting test\n");
    294             startWhen = getRelativeTimeNsec();
    295             pthread_cond_broadcast(&waitCond);
    296             pthread_mutex_unlock(&waitLock);
    297             break;
    298         }
    299         pthread_mutex_unlock(&waitLock);
    300         usleep(100000);
    301     }
    302 
    303     for (i = 0; i < THREAD_COUNT; i++) {
    304         void* retval;
    305         if (pthread_join(threads[i], &retval) != 0) {
    306             dvmFprintf(stderr, "thread join (%d) failed\n", i);
    307         }
    308     }
    309 
    310     endWhen = getRelativeTimeNsec();
    311     dvmFprintf(stdout, "All threads stopped, time is %.6fms\n",
    312         (endWhen - startWhen) / 1000000.0);
    313 
    314     /*
    315      * Show results; expecting:
    316      *
    317      * incTest = 5000000
    318      * decTest = -5000000
    319      * addTest = 7500000
    320      * casTest = 10000000
    321      * wideCasTest = 0x6600000077000000
    322      */
    323     dvmFprintf(stdout, "incTest = %d\n", incTest);
    324     dvmFprintf(stdout, "decTest = %d\n", decTest);
    325     dvmFprintf(stdout, "addTest = %d\n", addTest);
    326     dvmFprintf(stdout, "casTest = %d\n", casTest);
    327     dvmFprintf(stdout, "wideCasTest = 0x%llx\n", wideCasTest);
    328 
    329     /* do again, serially (SMP check) */
    330     startWhen = getRelativeTimeNsec();
    331     for (i = 0; i < THREAD_COUNT; i++) {
    332         doAtomicTest(i);
    333     }
    334     endWhen = getRelativeTimeNsec();
    335     dvmFprintf(stdout, "Same iterations done serially: time is %.6fms\n",
    336         (endWhen - startWhen) / 1000000.0);
    337 
    338     /*
    339      * Hard to do a meaningful thrash test on these, so just do a simple
    340      * function test.
    341      */
    342     andTest = 0xffd7fa96;
    343     orTest = 0x122221ff;
    344     android_atomic_and(0xfffdaf96, &andTest);
    345     android_atomic_or(0xdeaaeb00, &orTest);
    346     if (android_atomic_release_cas(failingCasTest+1, failingCasTest-1,
    347             &failingCasTest) == 0)
    348         dvmFprintf(stdout, "failing test did not fail!\n");
    349 
    350     dvmFprintf(stdout, "andTest = %#x\n", andTest);
    351     dvmFprintf(stdout, "orTest = %#x\n", orTest);
    352     dvmFprintf(stdout, "failingCasTest = %d\n", failingCasTest);
    353 
    354 #ifdef TEST_BIONIC
    355     /*
    356      * Quick function test on the bionic ops.
    357      */
    358     int prev;
    359     int tester = 7;
    360     prev = __atomic_inc(&tester);
    361     __atomic_inc(&tester);
    362     __atomic_inc(&tester);
    363     dvmFprintf(stdout, "bionic 3 inc: %d -> %d\n", prev, tester);
    364     prev = __atomic_dec(&tester);
    365     __atomic_dec(&tester);
    366     __atomic_dec(&tester);
    367     dvmFprintf(stdout, "bionic 3 dec: %d -> %d\n", prev, tester);
    368     prev = __atomic_swap(27, &tester);
    369     dvmFprintf(stdout, "bionic swap: %d -> %d\n", prev, tester);
    370     int swapok = __atomic_cmpxchg(27, 72, &tester);
    371     dvmFprintf(stdout, "bionic cmpxchg: %d (%d)\n", tester, swapok);
    372 #endif
    373 
    374     testAtomicSpeed();
    375 
    376     return 0;
    377 }
    378