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