Skip to content

Commit

Permalink
Add coldest & hottest weighted snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Sep 6, 2021
1 parent 317e647 commit 4f6ec75
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/qodana.yml
Expand Up @@ -38,7 +38,7 @@ jobs:
${{ runner.os }}-qodana-${{ github.ref }}
${{ runner.os }}-qodana-
- name: Qodana - Code Inspection
uses: JetBrains/qodana-action@v2.0-eap
uses: JetBrains/qodana-action@v2.1-eap
- uses: actions/upload-artifact@v2
with:
name: qodana-${{ env.JAVA_VERSION }}
Expand Down
Expand Up @@ -66,7 +66,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToLongFunction;

import org.checkerframework.checker.nullness.qual.Nullable;

Expand Down Expand Up @@ -2083,7 +2083,7 @@ public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @param expiry the calculator for the expiration time
* @param expiry the calculator for the write expiration time
* @param onlyIfAbsent a write is performed only if the key is not already associated with a value
* @return the prior value in or null if no mapping was found
*/
Expand Down Expand Up @@ -2728,14 +2728,16 @@ public Set<Entry<K, V>> entrySet() {
* Returns an unmodifiable snapshot map ordered in eviction order, either ascending or descending.
* Beware that obtaining the mappings is <em>NOT</em> a constant-time operation.
*
* @param limit the maximum number of entries
* @param limit the maximum size of the snapshot
* @param transformer a function that unwraps the value
* @param hottest the iteration order
* @param hottest the coldest or hottest iteration order
* @param weighted using unit or weight-based measurement
* @return an unmodifiable snapshot in a specified order
*/
@SuppressWarnings("GuardedByChecker")
Map<K, V> evictionOrder(int limit, Function<V, V> transformer, boolean hottest) {
Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> {
Map<K, V> evictionOrder(long limit, Function<V, V> transformer,
boolean hottest, boolean weighted) {
Iterable<Node<K, V>> iterable = () -> {
Comparator<Node<K, V>> comparator = Comparator.comparingInt(node -> {
K key = node.getKey();
return (key == null) ? 0 : frequencySketch().frequency(key);
Expand All @@ -2752,28 +2754,27 @@ Map<K, V> evictionOrder(int limit, Function<V, V> transformer, boolean hottest)
return PeekingIterator.concat(primary, accessOrderProtectedDeque().iterator());
}
};
return fixedSnapshot(iteratorSupplier, limit, transformer);

int initialCapacity = (int) Math.min(Math.min(limit, size()), Integer.SIZE);
ToLongFunction<Node<K, V>> measurer = weighted ? Node::getPolicyWeight : node -> 1L;
return fixedSnapshot(initialCapacity, limit, measurer, iterable, transformer);
}

/**
* Returns an unmodifiable snapshot map ordered in access expiration order, either ascending or
* descending. Beware that obtaining the mappings is <em>NOT</em> a constant-time operation.
*
* @param limit the maximum number of entries
* @param limit the maximum size of the snapshot
* @param transformer a function that unwraps the value
* @param oldest the iteration order
* @param oldest the youngest or oldest iteration order
* @return an unmodifiable snapshot in a specified order
*/
@SuppressWarnings("GuardedByChecker")
Map<K, V> expireAfterAccessOrder(int limit, Function<V, V> transformer, boolean oldest) {
if (!evicts()) {
Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> oldest
? accessOrderWindowDeque().iterator()
: accessOrderWindowDeque().descendingIterator();
return fixedSnapshot(iteratorSupplier, limit, transformer);
}

Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> {
int initialCapacity = Math.min(limit, size());
Iterable<Node<K, V>> iterable;
if (evicts()) {
iterable = () -> {
Comparator<Node<K, V>> comparator = Comparator.comparingLong(Node::getAccessTime);
PeekingIterator<Node<K, V>> first, second, third;
if (oldest) {
Expand All @@ -2788,52 +2789,63 @@ Map<K, V> expireAfterAccessOrder(int limit, Function<V, V> transformer, boolean
}
return PeekingIterator.comparing(
PeekingIterator.comparing(first, second, comparator), third, comparator);
};
return fixedSnapshot(iteratorSupplier, limit, transformer);
};
} else {
iterable = () -> oldest
? accessOrderWindowDeque().iterator()
: accessOrderWindowDeque().descendingIterator();
}
return fixedSnapshot(initialCapacity, limit, node -> 1L, iterable, transformer);
}

/**
* Returns an unmodifiable snapshot map ordered in write expiration order, either ascending or
* descending. Beware that obtaining the mappings is <em>NOT</em> a constant-time operation.
*
* @param limit the maximum number of entries
* @param limit the maximum size of the snapshot
* @param transformer a function that unwraps the value
* @param oldest the iteration order
* @param oldest the youngest or oldest iteration order
* @return an unmodifiable snapshot in a specified order
*/
@SuppressWarnings("GuardedByChecker")
Map<K, V> expireAfterWriteOrder(int limit, Function<V, V> transformer, boolean oldest) {
Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> oldest
int initialCapacity = Math.min(limit, size());
Iterable<Node<K, V>> iterable = () -> oldest
? writeOrderDeque().iterator()
: writeOrderDeque().descendingIterator();
return fixedSnapshot(iteratorSupplier, limit, transformer);
return fixedSnapshot(initialCapacity, limit, node -> 1L, iterable, transformer);
}

/**
* Returns an unmodifiable snapshot map ordered by the provided iterator. Beware that obtaining
* the mappings is <em>NOT</em> a constant-time operation.
*
* @param iteratorSupplier the iterator
* @param limit the maximum number of entries
* @param initialCapacity the initial capacity
* @param limit the maximum size of the snapshot
* @param iterable the ordered iteration of entries
* @param transformer a function that unwraps the value
* @param measurer a function that determines the entry's size
* @return an unmodifiable snapshot in the iterator's order
*/
Map<K, V> fixedSnapshot(Supplier<Iterator<Node<K, V>>> iteratorSupplier,
int limit, Function<V, V> transformer) {
Map<K, V> fixedSnapshot(int initialCapacity, long limit, ToLongFunction<Node<K, V>> measurer,
Iterable<Node<K, V>> iterable, Function<V, V> transformer) {
requireArgument(limit >= 0);
evictionLock.lock();
try {
maintenance(/* ignored */ null);

var iterator = iteratorSupplier.get();
int initialCapacity = Math.min(limit, size());
long size = 0;
var map = new LinkedHashMap<K, V>(initialCapacity);
while ((map.size() < limit) && iterator.hasNext()) {
Node<K, V> node = iterator.next();
for (var node : iterable) {
K key = node.getKey();
V value = transformer.apply(node.getValue());
if ((key != null) && (value != null) && node.isAlive()) {
map.put(key, value);
size += measurer.applyAsLong(node);
if (size <= limit) {
map.put(key, value);
} else {
break;
}
}
}
return Collections.unmodifiableMap(map);
Expand Down Expand Up @@ -3621,10 +3633,18 @@ final class BoundedEviction implements Eviction<K, V> {
}
}
@Override public Map<K, V> coldest(int limit) {
return cache.evictionOrder(limit, transformer, /* hottest */ false);
return cache.evictionOrder(limit, transformer, /* hottest */ false, /* weighted */ false);
}
@Override public Map<K, V> coldestWeighted(long weightLimit) {
return cache.evictionOrder(weightLimit, transformer,
/* hottest */ false, /* weighted */ true);
}
@Override public Map<K, V> hottest(int limit) {
return cache.evictionOrder(limit, transformer, /* hottest */ true);
return cache.evictionOrder(limit, transformer, /* hottest */ true, /* weighted */ false);
}
@Override public Map<K, V> hottestWeighted(long weightLimit) {
return cache.evictionOrder(weightLimit, transformer,
/* hottest */ true, /* weighted */ true);
}
}

Expand Down
Expand Up @@ -192,6 +192,26 @@ interface Eviction<K extends Object, V extends Object> {
*/
Map<K, V> coldest(@NonNegative int limit);

/**
* Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
* order of iteration is from the entries least likely to be retained (coldest) to the entries
* most likely to be retained (hottest). This order is determined by the eviction policy's best
* guess at the time of creating this snapshot view. If the cache is bounded by a maximum size
* rather than a maximum weight, then this method is equivalent to {@link #coldest(int)}.
* <p>
* Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of the page replacement policy, determining the retention ordering
* requires a traversal of the entries.
*
* @param weightLimit the maximum weight of the returned map (use {@link Long#MAX_VALUE} to
* disregard the limit)
* @return a snapshot view of the cache from coldest entry to the hottest
*/
default Map<K, V> coldestWeighted(@NonNegative long weightLimit) {
// This method was added & implemented in version 3.0.4
throw new UnsupportedOperationException();
}

/**
* Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
* order of iteration is from the entries most likely to be retained (hottest) to the entries
Expand All @@ -207,6 +227,26 @@ interface Eviction<K extends Object, V extends Object> {
* @return a snapshot view of the cache from hottest entry to the coldest
*/
Map<K, V> hottest(@NonNegative int limit);

/**
* Returns an unmodifiable snapshot {@link Map} view of the cache with ordered traversal. The
* order of iteration is from the entries most likely to be retained (hottest) to the entries
* least likely to be retained (coldest). This order is determined by the eviction policy's best
* guess at the time of creating this snapshot view. If the cache is bounded by a maximum size
* rather than a maximum weight, then this method is equivalent to {@link #hottest(int)}.
* <p>
* Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of the page replacement policy, determining the retention ordering
* requires a traversal of the entries.
*
* @param weightLimit the maximum weight of the returned map (use {@link Long#MAX_VALUE} to
* disregard the limit)
* @return a snapshot view of the cache from hottest entry to the coldest
*/
default Map<K, V> hottestWeighted(@NonNegative long weightLimit) {
// This method was added & implemented in version 3.0.4
throw new UnsupportedOperationException();
}
}

/** The low-level operations for a cache with a fixed expiration policy. */
Expand Down

0 comments on commit 4f6ec75

Please sign in to comment.