| /* |
| * Copyright (C) 2009 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. |
| */ |
| |
| /* |
| * Test the indirect reference table implementation. |
| */ |
| #include "Dalvik.h" |
| |
| #include <stdlib.h> |
| #include <sys/time.h> |
| |
| #ifndef NDEBUG |
| |
| #define DBUG_MSG ALOGI |
| |
| class Stopwatch { |
| public: |
| Stopwatch() { |
| reset(); |
| } |
| |
| void reset() { |
| start_ = now(); |
| } |
| |
| float elapsedSeconds() { |
| return (now() - start_) * 0.000001f; |
| } |
| |
| private: |
| u8 start_; |
| |
| static u8 now() { |
| #ifdef HAVE_POSIX_CLOCKS |
| struct timespec tm; |
| clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); |
| return tm.tv_sec * 1000000LL + tm.tv_nsec / 1000; |
| #else |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return tv.tv_sec * 1000000LL + tv.tv_usec; |
| #endif |
| } |
| }; |
| |
| /* |
| * Basic add/get/delete tests in an unsegmented table. |
| */ |
| static bool basicTest() |
| { |
| static const int kTableMax = 20; |
| IndirectRefTable irt; |
| IndirectRef iref0, iref1, iref2, iref3; |
| IndirectRef manyRefs[kTableMax]; |
| ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL); |
| Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); |
| Object* obj1 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); |
| Object* obj2 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); |
| Object* obj3 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); |
| const u4 cookie = IRT_FIRST_SEGMENT; |
| bool result = false; |
| |
| if (!irt.init(kTableMax/2, kTableMax, kIndirectKindGlobal)) { |
| return false; |
| } |
| |
| iref0 = (IndirectRef) 0x11110; |
| if (irt.remove(cookie, iref0)) { |
| ALOGE("unexpectedly successful removal"); |
| goto bail; |
| } |
| |
| /* |
| * Add three, check, remove in the order in which they were added. |
| */ |
| DBUG_MSG("+++ START fifo\n"); |
| iref0 = irt.add(cookie, obj0); |
| iref1 = irt.add(cookie, obj1); |
| iref2 = irt.add(cookie, obj2); |
| if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { |
| ALOGE("trivial add1 failed"); |
| goto bail; |
| } |
| |
| if (irt.get(iref0) != obj0 || |
| irt.get(iref1) != obj1 || |
| irt.get(iref2) != obj2) { |
| ALOGE("objects don't match expected values %p %p %p vs. %p %p %p", |
| irt.get(iref0), irt.get(iref1), irt.get(iref2), |
| obj0, obj1, obj2); |
| goto bail; |
| } else { |
| DBUG_MSG("+++ obj1=%p --> iref1=%p\n", obj1, iref1); |
| } |
| |
| if (!irt.remove(cookie, iref0) || |
| !irt.remove(cookie, iref1) || |
| !irt.remove(cookie, iref2)) |
| { |
| ALOGE("fifo deletion failed"); |
| goto bail; |
| } |
| |
| /* table should be empty now */ |
| if (irt.capacity() != 0) { |
| ALOGE("fifo del not empty"); |
| goto bail; |
| } |
| |
| /* get invalid entry (off the end of the list) */ |
| if (irt.get(iref0) != kInvalidIndirectRefObject) { |
| ALOGE("stale entry get succeeded unexpectedly"); |
| goto bail; |
| } |
| |
| /* |
| * Add three, remove in the opposite order. |
| */ |
| DBUG_MSG("+++ START lifo\n"); |
| iref0 = irt.add(cookie, obj0); |
| iref1 = irt.add(cookie, obj1); |
| iref2 = irt.add(cookie, obj2); |
| if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { |
| ALOGE("trivial add2 failed"); |
| goto bail; |
| } |
| |
| if (!irt.remove(cookie, iref2) || |
| !irt.remove(cookie, iref1) || |
| !irt.remove(cookie, iref0)) |
| { |
| ALOGE("lifo deletion failed"); |
| goto bail; |
| } |
| |
| /* table should be empty now */ |
| if (irt.capacity() != 0) { |
| ALOGE("lifo del not empty"); |
| goto bail; |
| } |
| |
| /* |
| * Add three, remove middle / middle / bottom / top. (Second attempt |
| * to remove middle should fail.) |
| */ |
| DBUG_MSG("+++ START unorder\n"); |
| iref0 = irt.add(cookie, obj0); |
| iref1 = irt.add(cookie, obj1); |
| iref2 = irt.add(cookie, obj2); |
| if (iref0 == NULL || iref1 == NULL || iref2 == NULL) { |
| ALOGE("trivial add3 failed"); |
| goto bail; |
| } |
| |
| if (irt.capacity() != 3) { |
| ALOGE("expected 3 entries, found %d", irt.capacity()); |
| goto bail; |
| } |
| |
| if (!irt.remove(cookie, iref1) || irt.remove(cookie, iref1)) { |
| ALOGE("unorder deletion1 failed"); |
| goto bail; |
| } |
| |
| /* get invalid entry (from hole) */ |
| if (irt.get(iref1) != kInvalidIndirectRefObject) { |
| ALOGE("hole get succeeded unexpectedly"); |
| goto bail; |
| } |
| |
| if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) { |
| ALOGE("unorder deletion2 failed"); |
| goto bail; |
| } |
| |
| /* table should be empty now */ |
| if (irt.capacity() != 0) { |
| ALOGE("unorder del not empty"); |
| goto bail; |
| } |
| |
| /* |
| * Add four entries. Remove #1, add new entry, verify that table size |
| * is still 4 (i.e. holes are getting filled). Remove #1 and #3, verify |
| * that we delete one and don't hole-compact the other. |
| */ |
| DBUG_MSG("+++ START hole fill\n"); |
| iref0 = irt.add(cookie, obj0); |
| iref1 = irt.add(cookie, obj1); |
| iref2 = irt.add(cookie, obj2); |
| iref3 = irt.add(cookie, obj3); |
| if (iref0 == NULL || iref1 == NULL || iref2 == NULL || iref3 == NULL) { |
| ALOGE("trivial add4 failed"); |
| goto bail; |
| } |
| if (!irt.remove(cookie, iref1)) { |
| ALOGE("remove 1 of 4 failed"); |
| goto bail; |
| } |
| iref1 = irt.add(cookie, obj1); |
| if (irt.capacity() != 4) { |
| ALOGE("hole not filled"); |
| goto bail; |
| } |
| if (!irt.remove(cookie, iref1) || !irt.remove(cookie, iref3)) { |
| ALOGE("remove 1/3 failed"); |
| goto bail; |
| } |
| if (irt.capacity() != 3) { |
| ALOGE("should be 3 after two deletions"); |
| goto bail; |
| } |
| if (!irt.remove(cookie, iref2) || !irt.remove(cookie, iref0)) { |
| ALOGE("remove 2/0 failed"); |
| goto bail; |
| } |
| if (irt.capacity() != 0) { |
| ALOGE("not empty after split remove"); |
| goto bail; |
| } |
| |
| /* |
| * Add an entry, remove it, add a new entry, and try to use the original |
| * iref. They have the same slot number but are for different objects. |
| * With the extended checks in place, this should fail. |
| */ |
| DBUG_MSG("+++ START switched\n"); |
| iref0 = irt.add(cookie, obj0); |
| irt.remove(cookie, iref0); |
| iref1 = irt.add(cookie, obj1); |
| if (irt.remove(cookie, iref0)) { |
| ALOGE("mismatched del succeeded (%p vs %p)", iref0, iref1); |
| goto bail; |
| } |
| if (!irt.remove(cookie, iref1)) { |
| ALOGE("switched del failed"); |
| goto bail; |
| } |
| if (irt.capacity() != 0) { |
| ALOGE("switching del not empty"); |
| goto bail; |
| } |
| |
| /* |
| * Same as above, but with the same object. A more rigorous checker |
| * (e.g. with slot serialization) will catch this. |
| */ |
| DBUG_MSG("+++ START switched same object\n"); |
| iref0 = irt.add(cookie, obj0); |
| irt.remove(cookie, iref0); |
| iref1 = irt.add(cookie, obj0); |
| if (iref0 != iref1) { |
| /* try 0, should not work */ |
| if (irt.remove(cookie, iref0)) { |
| ALOGE("temporal del succeeded (%p vs %p)", iref0, iref1); |
| goto bail; |
| } |
| } |
| if (!irt.remove(cookie, iref1)) { |
| ALOGE("temporal cleanup failed"); |
| goto bail; |
| } |
| if (irt.capacity() != 0) { |
| ALOGE("temporal del not empty"); |
| goto bail; |
| } |
| |
| DBUG_MSG("+++ START null lookup\n"); |
| if (irt.get(NULL) != kInvalidIndirectRefObject) { |
| ALOGE("null lookup succeeded"); |
| goto bail; |
| } |
| |
| DBUG_MSG("+++ START stale lookup\n"); |
| iref0 = irt.add(cookie, obj0); |
| irt.remove(cookie, iref0); |
| if (irt.get(iref0) != kInvalidIndirectRefObject) { |
| ALOGE("stale lookup succeeded"); |
| goto bail; |
| } |
| |
| /* |
| * Test table overflow. |
| */ |
| DBUG_MSG("+++ START overflow\n"); |
| int i; |
| for (i = 0; i < kTableMax; i++) { |
| manyRefs[i] = irt.add(cookie, obj0); |
| if (manyRefs[i] == NULL) { |
| ALOGE("Failed adding %d of %d", i, kTableMax); |
| goto bail; |
| } |
| } |
| if (irt.add(cookie, obj0) != NULL) { |
| ALOGE("Table overflow succeeded"); |
| goto bail; |
| } |
| if (irt.capacity() != (size_t)kTableMax) { |
| ALOGE("Expected %d entries, found %d", kTableMax, irt.capacity()); |
| goto bail; |
| } |
| irt.dump("table with 20 entries, all filled"); |
| for (i = 0; i < kTableMax-1; i++) { |
| if (!irt.remove(cookie, manyRefs[i])) { |
| ALOGE("multi-remove failed at %d", i); |
| goto bail; |
| } |
| } |
| irt.dump("table with 20 entries, 19 of them holes"); |
| /* because of removal order, should have 20 entries, 19 of them holes */ |
| if (irt.capacity() != (size_t)kTableMax) { |
| ALOGE("Expected %d entries (with holes), found %d", |
| kTableMax, irt.capacity()); |
| goto bail; |
| } |
| if (!irt.remove(cookie, manyRefs[kTableMax-1])) { |
| ALOGE("multi-remove final failed"); |
| goto bail; |
| } |
| if (irt.capacity() != 0) { |
| ALOGE("multi-del not empty"); |
| goto bail; |
| } |
| |
| /* Done */ |
| DBUG_MSG("+++ basic test complete\n"); |
| result = true; |
| |
| bail: |
| irt.destroy(); |
| return result; |
| } |
| |
| static bool performanceTest() |
| { |
| static const int kTableMax = 100; |
| IndirectRefTable irt; |
| IndirectRef manyRefs[kTableMax]; |
| ClassObject* clazz = dvmFindClass("Ljava/lang/Object;", NULL); |
| Object* obj0 = dvmAllocObject(clazz, ALLOC_DONT_TRACK); |
| const u4 cookie = IRT_FIRST_SEGMENT; |
| const int kLoops = 100000; |
| Stopwatch stopwatch; |
| |
| DBUG_MSG("+++ START performance\n"); |
| |
| if (!irt.init(kTableMax, kTableMax, kIndirectKindGlobal)) { |
| return false; |
| } |
| |
| stopwatch.reset(); |
| for (int loop = 0; loop < kLoops; loop++) { |
| for (int i = 0; i < kTableMax; i++) { |
| manyRefs[i] = irt.add(cookie, obj0); |
| } |
| for (int i = 0; i < kTableMax; i++) { |
| irt.remove(cookie, manyRefs[i]); |
| } |
| } |
| DBUG_MSG("Add/remove %d objects FIFO order, %d iterations, %0.3fms / iteration", |
| kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); |
| |
| stopwatch.reset(); |
| for (int loop = 0; loop < kLoops; loop++) { |
| for (int i = 0; i < kTableMax; i++) { |
| manyRefs[i] = irt.add(cookie, obj0); |
| } |
| for (int i = kTableMax; i-- > 0; ) { |
| irt.remove(cookie, manyRefs[i]); |
| } |
| } |
| DBUG_MSG("Add/remove %d objects LIFO order, %d iterations, %0.3fms / iteration", |
| kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); |
| |
| for (int i = 0; i < kTableMax; i++) { |
| manyRefs[i] = irt.add(cookie, obj0); |
| } |
| stopwatch.reset(); |
| for (int loop = 0; loop < kLoops; loop++) { |
| for (int i = 0; i < kTableMax; i++) { |
| irt.get(manyRefs[i]); |
| } |
| } |
| DBUG_MSG("Get %d objects, %d iterations, %0.3fms / iteration", |
| kTableMax, kLoops, stopwatch.elapsedSeconds() * 1000 / kLoops); |
| for (int i = kTableMax; i-- > 0; ) { |
| irt.remove(cookie, manyRefs[i]); |
| } |
| |
| irt.destroy(); |
| return true; |
| } |
| |
| /* |
| * Some quick tests. |
| */ |
| bool dvmTestIndirectRefTable() |
| { |
| if (!basicTest()) { |
| ALOGE("IRT basic test failed"); |
| return false; |
| } |
| |
| if (!performanceTest()) { |
| ALOGE("IRT performance test failed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| #endif /*NDEBUG*/ |