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 */
getRelativeTimeNsec(void)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
incr(void)97 static void incr(void)
98 {
99 incTest++;
100 }
decr(void)101 static void decr(void)
102 {
103 decTest--;
104 }
add(int32_t addVal)105 static void add(int32_t addVal)
106 {
107 addTest += addVal;
108 }
compareAndSwap(int32_t oldVal,int32_t newVal,int32_t * addr)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 }
compareAndSwapWide(int64_t oldVal,int64_t newVal,int64_t * addr)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 */
doAtomicTest(int num)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 */
atomicTest(void * arg)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 */
testAtomicSpeedSub(int repeatCount)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
testAtomicSpeed(void)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 */
dvmTestAtomicSpeed(void)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