/* * Copyright (C) 2008 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. */ /* * Mutex-free cache for key1+key2=value. */ #ifndef DALVIK_ATOMICCACHE_H_ #define DALVIK_ATOMICCACHE_H_ /* * If set to "1", gather some stats on our caching success rate. */ #define CALC_CACHE_STATS 0 /* * One entry in the cache. We store two keys (e.g. the classes that are * arguments to "instanceof") and one result (e.g. a boolean value). * * Must be exactly 16 bytes. */ struct AtomicCacheEntry { u4 key1; u4 key2; u4 value; volatile u4 version; /* version and lock flag */ }; /* * One cache. * * Thought: we might be able to save a few cycles by storing the cache * struct and "entries" separately, avoiding an indirection. (We already * handle "numEntries" separately in ATOMIC_CACHE_LOOKUP.) */ struct AtomicCache { AtomicCacheEntry* entries; /* array of entries */ int numEntries; /* #of entries, must be power of 2 */ void* entryAlloc; /* memory allocated for entries */ /* cache stats; note we don't guarantee atomic increments for these */ int trivial; /* cache access not required */ int fail; /* contention failure */ int hits; /* found entry in cache */ int misses; /* entry was for other keys */ int fills; /* entry was empty */ }; /* * Do a cache lookup. We need to be able to read and write entries * atomically. There are a couple of ways to do this: * (1) Have a global lock. A mutex is too heavy, so instead we would use * an atomic flag. If the flag is set, we could sit and spin, * but if we're a high-priority thread that may cause a lockup. * Better to just ignore the cache and do the full computation. * (2) Have a "version" that gets incremented atomically when a write * begins and again when it completes. Compare the version before * and after doing reads. So long as we have memory barriers in the * right place the compiler and CPU will do the right thing, allowing * us to skip atomic ops in the common read case. The table updates * are expensive, requiring two writes with barriers. We also need * some sort of lock to ensure that nobody else tries to start an * update while we're in the middle of one. * * We expect a 95+% hit rate for the things we use this for, so #2 is * much better than #1. * * _cache is an AtomicCache* * _cacheSize is _cache->cacheSize (can save a cycle avoiding the lookup) * _key1, _key2 are the keys * * Define a function ATOMIC_CACHE_CALC that returns a 32-bit value. This * will be invoked when we need to compute the value. * * Returns the value. */ #if CALC_CACHE_STATS > 0 # define CACHE_XARG(_value) ,_value #else # define CACHE_XARG(_value) #endif #define ATOMIC_CACHE_LOOKUP(_cache, _cacheSize, _key1, _key2) ({ \ AtomicCacheEntry* pEntry; \ int hash; \ u4 firstVersion, secondVersion; \ u4 value; \ \ /* simple hash function */ \ hash = (((u4)(_key1) >> 2) ^ (u4)(_key2)) & ((_cacheSize)-1); \ pEntry = (_cache)->entries + hash; \ \ firstVersion = android_atomic_acquire_load((int32_t*)&pEntry->version); \ \ if (pEntry->key1 == (u4)(_key1) && pEntry->key2 == (u4)(_key2)) { \ /* \ * The fields match. Get the value, then read the version a \ * second time to verify that we didn't catch a partial update. \ * We're also hosed if "firstVersion" was odd, indicating that \ * an update was in progress before we got here (unlikely). \ */ \ value = android_atomic_acquire_load((int32_t*) &pEntry->value); \ secondVersion = pEntry->version; \ \ if ((firstVersion & 0x01) != 0 || firstVersion != secondVersion) { \ /* \ * We clashed with another thread. Instead of sitting and \ * spinning, which might not complete if we're a high priority \ * thread, just do the regular computation. \ */ \ if (CALC_CACHE_STATS) \ (_cache)->fail++; \ value = (u4) ATOMIC_CACHE_CALC; \ } else { \ /* all good */ \ if (CALC_CACHE_STATS) \ (_cache)->hits++; \ } \ } else { \ /* \ * Compute the result and update the cache. We really want this \ * to happen in a different method -- it makes the ARM frame \ * setup for this method simpler, which gives us a ~10% speed \ * boost. \ */ \ value = (u4) ATOMIC_CACHE_CALC; \ dvmUpdateAtomicCache((u4) (_key1), (u4) (_key2), value, pEntry, \ firstVersion CACHE_XARG(_cache) ); \ } \ value; \ }) /* * Allocate a cache. */ AtomicCache* dvmAllocAtomicCache(int numEntries); /* * Free a cache. */ void dvmFreeAtomicCache(AtomicCache* cache); /* * Update a cache entry. * * Making the last argument optional, instead of merely unused, saves us * a few percent in the ATOMIC_CACHE_LOOKUP time. */ void dvmUpdateAtomicCache(u4 key1, u4 key2, u4 value, AtomicCacheEntry* pEntry, u4 firstVersion #if CALC_CACHE_STATS > 0 , AtomicCache* pCache #endif ); /* * Debugging. */ void dvmDumpAtomicCacheStats(const AtomicCache* pCache); #endif // DALVIK_ATOMICCACHE_H_