From: Robert Varga Date: Tue, 22 Dec 2015 15:21:31 +0000 (+0100) Subject: BUG-4803: optimize unordered cache X-Git-Tag: release/beryllium~55 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=yangtools.git;a=commitdiff_plain;h=6ba98a2fab390cafe7a98e466189ea8df09cdeb9 BUG-4803: optimize unordered cache This patch side-steps the need to perform kopu operations when the offsets are already constructed. It also optimizes the number of ImmutableSet instances present in the system. Change-Id: I8a9b42e053d88c85bf5e75eb5a58da9bd08ce6d0 Signed-off-by: Robert Varga --- diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java index b7ba220379..b18c218f21 100644 --- a/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java +++ b/common/util/src/main/java/org/opendaylight/yangtools/util/OffsetMapCache.java @@ -9,6 +9,7 @@ package org.opendaylight.yangtools.util; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Verify; +import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -21,64 +22,55 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; final class OffsetMapCache { - private static final LoadingCache, Map> CACHE = - CacheBuilder.newBuilder().weakValues().build(new CacheLoader, Map>() { + /* + * Cache for offsets where order matters. The key is a List, which defines the iteration order. Since we want + * to retain this order, it is okay to use a simple LoadingCache. + */ + private static final LoadingCache, Map> ORDERED_CACHE = + CacheBuilder.newBuilder().weakValues().build(new CacheLoader, Map>() { @Override - public Map load(final Collection key) { - final Builder b = ImmutableMap.builder(); - int i = 0; - - for (Object arg : key) { - b.put(arg, i++); - } - - return b.build(); + public Map load(final List key) { + return createMap(key); } }); + /* + * Cache for offsets where order does not mapper. The key is a Set of elements. We use manual two-stage loading + * because of the nature of the objects we store as values, which is ImmutableMaps. An ImmutableMap, when queried + * for keys (as is done in ImmutableOffsetMap.keySet()), will instantiate an ImmutableSet to hold these keys. It + * would be wasteful to use one Set for lookup only to have the map have an exact copy. + * + * We perform the first look up using a Set (which may come from the user, for example via + * ImmutableOffsetMap.unorderedCopyOf()), hence potentially saving a copy operation. If we fail to find an entry, + * we construct the map and put it conditionally with Map.keySet() as the key. This will detect concurrent loading + * and also lead to the cache and the map sharing the same Set. + */ + private static final Cache, Map> UNORDERED_CACHE = + CacheBuilder.newBuilder().weakValues().build(); private OffsetMapCache() { throw new UnsupportedOperationException(); } - @SuppressWarnings("unchecked") - private static Map offsets(final Collection args) { - return (Map) CACHE.getUnchecked(args); - } - @VisibleForTesting static void invalidateCache() { - CACHE.invalidateAll(); + ORDERED_CACHE.invalidateAll(); + UNORDERED_CACHE.invalidateAll(); } + @SuppressWarnings("unchecked") static Map orderedOffsets(final Collection args) { if (args.size() == 1) { return unorderedOffsets(args); } - return offsets(ImmutableList.copyOf(args)); + return (Map) ORDERED_CACHE.getUnchecked(ImmutableList.copyOf(args)); } static Map unorderedOffsets(final Collection args) { - if (args instanceof SingletonSet) { - return offsets(args); - } - - return offsets(ImmutableSet.copyOf(args)); - } - - private static V[] adjustArray(final Map offsets, final List keys, final V[] array) { - @SuppressWarnings("unchecked") - final V[] ret = (V[]) Array.newInstance(array.getClass().getComponentType(), array.length); - - int i = 0; - for (final K k : keys) { - final Integer o = Verify.verifyNotNull(offsets.get(k), "Key %s not present in offsets %s", k, offsets); - ret[o] = array[i++]; - } - - return ret; + return unorderedOffsets(args instanceof Set ? (Set)args : ImmutableSet.copyOf(args)); } static V[] adjustedArray(final Map offsets, final List keys, final V[] array) { @@ -98,4 +90,40 @@ final class OffsetMapCache { return array; } + + private static Map createMap(final Collection keys) { + final Builder b = ImmutableMap.builder(); + int i = 0; + + for (T arg : keys) { + b.put(arg, i++); + } + + return b.build(); + } + + @SuppressWarnings("unchecked") + private static Map unorderedOffsets(final Set args) { + final Map existing = (Map) UNORDERED_CACHE.getIfPresent(args); + if (existing != null) { + return existing; + } + + final Map newMap = createMap(args); + final Map raced = UNORDERED_CACHE.asMap().putIfAbsent(newMap.keySet(), newMap); + return raced == null ? newMap : (Map)raced; + } + + private static V[] adjustArray(final Map offsets, final List keys, final V[] array) { + @SuppressWarnings("unchecked") + final V[] ret = (V[]) Array.newInstance(array.getClass().getComponentType(), array.length); + + int i = 0; + for (final K k : keys) { + final Integer o = Verify.verifyNotNull(offsets.get(k), "Key %s not present in offsets %s", k, offsets); + ret[o] = array[i++]; + } + + return ret; + } }