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 int swapTest = 0; 65 static int64_t wideCasTest = 0x6600000077000000LL; 66 67 /* 68 * Get a relative time value. 69 */ 70 static int64_t getRelativeTimeNsec(void) 71 { 72 #define HAVE_POSIX_CLOCKS 73 #ifdef HAVE_POSIX_CLOCKS 74 struct timespec now; 75 clock_gettime(CLOCK_MONOTONIC, &now); 76 return (int64_t) now.tv_sec*1000000000LL + now.tv_nsec; 77 #else 78 struct timeval now; 79 gettimeofday(&now, NULL); 80 return (int64_t) now.tv_sec*1000000000LL + now.tv_usec * 1000LL; 81 #endif 82 } 83 84 85 /* 86 * Non-atomic implementations, for comparison. 87 * 88 * If these get inlined the compiler may figure out what we're up to and 89 * completely elide the operations. 90 */ 91 static void incr(void) __attribute__((noinline)); 92 static void decr(void) __attribute__((noinline)); 93 static void add(int addVal) __attribute__((noinline)); 94 static int compareAndSwap(int oldVal, int newVal, int* addr) __attribute__((noinline)); 95 static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr) __attribute__((noinline)); 96 97 static void incr(void) 98 { 99 incTest++; 100 } 101 static void decr(void) 102 { 103 decTest--; 104 } 105 static void add(int32_t addVal) 106 { 107 addTest += addVal; 108 } 109 static int compareAndSwap(int32_t oldVal, int32_t newVal, int32_t* addr) 110 { 111 if (*addr == oldVal) { 112 *addr = newVal; 113 return 0; 114 } 115 return 1; 116 } 117 static int compareAndSwapWide(int64_t oldVal, int64_t newVal, int64_t* addr) 118 { 119 if (*addr == oldVal) { 120 *addr = newVal; 121 return 0; 122 } 123 return 1; 124 } 125 126 /* 127 * Exercise several of the atomic ops. 128 */ 129 static void doAtomicTest(int num) 130 { 131 int addVal = (num & 0x01) + 1; 132 133 int i; 134 for (i = 0; i < ITERATION_COUNT; i++) { 135 if (USE_ATOMIC) { 136 android_atomic_inc(&incTest); 137 android_atomic_dec(&decTest); 138 android_atomic_add(addVal, &addTest); 139 140 int val; 141 do { 142 val = casTest; 143 } while (android_atomic_release_cas(val, val+3, &casTest) != 0); 144 do { 145 val = casTest; 146 } while (android_atomic_acquire_cas(val, val-1, &casTest) != 0); 147 148 int64_t wval; 149 do { 150 wval = dvmQuasiAtomicRead64(&wideCasTest); 151 } while (dvmQuasiAtomicCas64(wval, 152 wval + 0x0000002000000001LL, &wideCasTest) != 0); 153 do { 154 wval = dvmQuasiAtomicRead64(&wideCasTest); 155 } while (dvmQuasiAtomicCas64(wval, 156 wval - 0x0000002000000001LL, &wideCasTest) != 0); 157 } else { 158 incr(); 159 decr(); 160 add(addVal); 161 162 int val; 163 do { 164 val = casTest; 165 } while (compareAndSwap(val, val+3, &casTest) != 0); 166 do { 167 val = casTest; 168 } while (compareAndSwap(val, val-1, &casTest) != 0); 169 170 int64_t wval; 171 do { 172 wval = wideCasTest; 173 } while (compareAndSwapWide(wval, 174 wval + 0x0000002000000001LL, &wideCasTest) != 0); 175 do { 176 wval = wideCasTest; 177 } while (compareAndSwapWide(wval, 178 wval - 0x0000002000000001LL, &wideCasTest) != 0); 179 } 180 } 181 } 182 183 /* 184 * Entry point for multi-thread test. 185 */ 186 static void* atomicTest(void* arg) 187 { 188 pthread_mutex_lock(&waitLock); 189 threadsStarted++; 190 pthread_cond_wait(&waitCond, &waitLock); 191 pthread_mutex_unlock(&waitLock); 192 193 doAtomicTest((int) arg); 194 195 return NULL; 196 } 197 198 /* lifted from a VM test */ 199 static int64_t testAtomicSpeedSub(int repeatCount) 200 { 201 static int value = 7; 202 int* valuePtr = &value; 203 int64_t start, end; 204 int i; 205 206 start = getRelativeTimeNsec(); 207 208 for (i = repeatCount / 10; i != 0; i--) { 209 if (USE_ATOMIC) { 210 // succeed 10x 211 (void) android_atomic_release_cas(7, 7, valuePtr); 212 (void) android_atomic_release_cas(7, 7, valuePtr); 213 (void) android_atomic_release_cas(7, 7, valuePtr); 214 (void) android_atomic_release_cas(7, 7, valuePtr); 215 (void) android_atomic_release_cas(7, 7, valuePtr); 216 (void) android_atomic_release_cas(7, 7, valuePtr); 217 (void) android_atomic_release_cas(7, 7, valuePtr); 218 (void) android_atomic_release_cas(7, 7, valuePtr); 219 (void) android_atomic_release_cas(7, 7, valuePtr); 220 (void) android_atomic_release_cas(7, 7, valuePtr); 221 } else { 222 // succeed 10x 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 compareAndSwap(7, 7, valuePtr); 233 } 234 } 235 236 end = getRelativeTimeNsec(); 237 238 dvmFprintf(stdout, "."); 239 fflush(stdout); 240 return end - start; 241 } 242 243 static void testAtomicSpeed(void) 244 { 245 static const int kIterations = 10; 246 static const int kRepeatCount = 5 * 1000 * 1000; 247 static const int kDelay = 50 * 1000; 248 int64_t results[kIterations]; 249 int i; 250 251 for (i = 0; i < kIterations; i++) { 252 results[i] = testAtomicSpeedSub(kRepeatCount); 253 usleep(kDelay); 254 } 255 256 dvmFprintf(stdout, "\n"); 257 dvmFprintf(stdout, "%s speed test results (%d per iteration):\n", 258 USE_ATOMIC ? "Atomic" : "Non-atomic", kRepeatCount); 259 for (i = 0; i < kIterations; i++) { 260 dvmFprintf(stdout, 261 " %2d: %.3fns\n", i, (double) results[i] / kRepeatCount); 262 } 263 } 264 265 /* 266 * Start tests, show results. 267 */ 268 bool dvmTestAtomicSpeed(void) 269 { 270 pthread_t threads[THREAD_COUNT]; 271 void *(*startRoutine)(void*) = atomicTest; 272 int64_t startWhen, endWhen; 273 274 #if defined(__ARM_ARCH__) 275 dvmFprintf(stdout, "__ARM_ARCH__ is %d\n", __ARM_ARCH__); 276 #endif 277 #if defined(ANDROID_SMP) 278 dvmFprintf(stdout, "ANDROID_SMP is %d\n", ANDROID_SMP); 279 #endif 280 dvmFprintf(stdout, "Creating threads\n"); 281 282 int i; 283 for (i = 0; i < THREAD_COUNT; i++) { 284 void* arg = (void*) i; 285 if (pthread_create(&threads[i], NULL, startRoutine, arg) != 0) { 286 dvmFprintf(stderr, "thread create failed\n"); 287 } 288 } 289 290 /* wait for all the threads to reach the starting line */ 291 while (1) { 292 pthread_mutex_lock(&waitLock); 293 if (threadsStarted == THREAD_COUNT) { 294 dvmFprintf(stdout, "Starting test\n"); 295 startWhen = getRelativeTimeNsec(); 296 pthread_cond_broadcast(&waitCond); 297 pthread_mutex_unlock(&waitLock); 298 break; 299 } 300 pthread_mutex_unlock(&waitLock); 301 usleep(100000); 302 } 303 304 for (i = 0; i < THREAD_COUNT; i++) { 305 void* retval; 306 if (pthread_join(threads[i], &retval) != 0) { 307 dvmFprintf(stderr, "thread join (%d) failed\n", i); 308 } 309 } 310 311 endWhen = getRelativeTimeNsec(); 312 dvmFprintf(stdout, "All threads stopped, time is %.6fms\n", 313 (endWhen - startWhen) / 1000000.0); 314 315 /* 316 * Show results; expecting: 317 * 318 * incTest = 5000000 319 * decTest = -5000000 320 * addTest = 7500000 321 * casTest = 10000000 322 * wideCasTest = 0x6600000077000000 323 */ 324 dvmFprintf(stdout, "incTest = %d\n", incTest); 325 dvmFprintf(stdout, "decTest = %d\n", decTest); 326 dvmFprintf(stdout, "addTest = %d\n", addTest); 327 dvmFprintf(stdout, "casTest = %d\n", casTest); 328 dvmFprintf(stdout, "wideCasTest = 0x%llx\n", wideCasTest); 329 330 /* do again, serially (SMP check) */ 331 startWhen = getRelativeTimeNsec(); 332 for (i = 0; i < THREAD_COUNT; i++) { 333 doAtomicTest(i); 334 } 335 endWhen = getRelativeTimeNsec(); 336 dvmFprintf(stdout, "Same iterations done serially: time is %.6fms\n", 337 (endWhen - startWhen) / 1000000.0); 338 339 /* 340 * Hard to do a meaningful thrash test on these, so just do a simple 341 * function test. 342 */ 343 andTest = 0xffd7fa96; 344 orTest = 0x122221ff; 345 swapTest = 0x11111111; 346 android_atomic_and(0xfffdaf96, &andTest); 347 android_atomic_or(0xdeaaeb00, &orTest); 348 int oldSwap = android_atomic_swap(0x22222222, &swapTest); 349 int oldSwap2 = android_atomic_swap(0x33333333, &swapTest); 350 if (android_atomic_release_cas(failingCasTest+1, failingCasTest-1, 351 &failingCasTest) == 0) 352 dvmFprintf(stdout, "failing test did not fail!\n"); 353 354 dvmFprintf(stdout, "andTest = 0x%x\n", andTest); 355 dvmFprintf(stdout, "orTest = 0x%x\n", orTest); 356 dvmFprintf(stdout, "swapTest = 0x%x -> 0x%x\n", oldSwap, oldSwap2); 357 dvmFprintf(stdout, "swapTest = 0x%x -> 0x%x\n", oldSwap2, swapTest); 358 dvmFprintf(stdout, "failingCasTest = %d\n", failingCasTest); 359 360 #ifdef TEST_BIONIC 361 /* 362 * Quick function test on the bionic ops. 363 */ 364 int prev; 365 int tester = 7; 366 prev = __atomic_inc(&tester); 367 __atomic_inc(&tester); 368 __atomic_inc(&tester); 369 dvmFprintf(stdout, "bionic 3 inc: %d -> %d\n", prev, tester); 370 prev = __atomic_dec(&tester); 371 __atomic_dec(&tester); 372 __atomic_dec(&tester); 373 dvmFprintf(stdout, "bionic 3 dec: %d -> %d\n", prev, tester); 374 prev = __atomic_swap(27, &tester); 375 dvmFprintf(stdout, "bionic swap: %d -> %d\n", prev, tester); 376 int swapok = __atomic_cmpxchg(27, 72, &tester); 377 dvmFprintf(stdout, "bionic cmpxchg: %d (%d)\n", tester, swapok); 378 #endif 379 380 testAtomicSpeed(); 381 382 return 0; 383 } 384