From 00c20dae6b71f80a2dec3c9338a865ec92a5cf3f Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 19 Aug 2019 18:58:24 +0200 Subject: [PATCH] use long instead of Instant for time Working with longs is faster and requires less cache. The space in L123 caches is precious. --- .../lucares/utils/cache/HotEntryCache.java | 135 +++++++++--------- .../utils/cache/HotEntryCacheTest.java | 8 +- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java b/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java index 67ce8bc..5559315 100644 --- a/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java +++ b/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java @@ -2,7 +2,6 @@ package org.lucares.utils.cache; import java.time.Clock; import java.time.Duration; -import java.time.Instant; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Set; @@ -31,8 +30,16 @@ import org.slf4j.LoggerFactory; * Caching frameworks like EhCache only evict entries when a new entry is added. * That might not be desired, e.g. when the cached objects block resources. *

- * This cache is a simple wrapper for a ConcurrentHashMap that evicts entries - * after timeToLive+5s. + * This cache is a wrapper for a ConcurrentHashMap that evicts entries after the + * specified timeToLive. The cache spawns two threads. + *

    + *
  1. One for updating time. It is far too expensive to call + * {@link System#currentTimeMillis()} or something similar whenever a timestamp + * is needed. Therefore we have a thread that updates a member periodically. + *
  2. One for evicting elements. So that elements can be evicted based on their + * time to live. Other cache implementations do this only when elements are + * added to the cache. + *
*/ public class HotEntryCache { @@ -67,11 +74,11 @@ public class HotEntryCache { } private final static class Entry { - private Instant lastAccessed; + private long lastAccessed; private V value; - public Entry(final V value, final Instant creationTime) { + public Entry(final V value, final long creationTime) { this.value = value; lastAccessed = creationTime; } @@ -84,11 +91,11 @@ public class HotEntryCache { this.value = value; } - public Instant getLastAccessed() { + public long getLastAccessed() { return lastAccessed; } - public void touch(final Instant instant) { + public void touch(final long instant) { lastAccessed = instant; } } @@ -110,8 +117,8 @@ public class HotEntryCache { } } - public void setUpdateInterval(final Duration updateInterval) { - this.updateInterval = Math.max(updateInterval.toMillis(), 1); + public void setUpdateInterval(final long updateInterval) { + this.updateInterval = Math.max(updateInterval, 1); interrupt(); } @@ -147,8 +154,8 @@ public class HotEntryCache { private final AtomicReference> future = new AtomicReference<>(null); private final Object lock = new Object(); - private Duration minSleepPeriod = Duration.ofSeconds(5); - private Duration maxSleepPeriod = Duration.ofDays(1); + private long minSleepPeriodInMs = Duration.ofSeconds(5).toMillis(); + private long maxSleepPeriodInMs = Duration.ofDays(1).toMillis(); public EvictionThread() { setDaemon(true); @@ -161,17 +168,17 @@ public class HotEntryCache { } } - private Duration getMinSleepPeriod() { - return minSleepPeriod; + private long getMinSleepPeriod() { + return minSleepPeriodInMs; } - private Duration getMaxSleepPeriod() { - return maxSleepPeriod; + private long getMaxSleepPeriod() { + return maxSleepPeriodInMs; } @Override public void run() { - Duration timeToNextEviction = maxSleepPeriod; + long timeToNextEviction = maxSleepPeriodInMs; while (true) { sleepToNextEviction(timeToNextEviction); @@ -179,7 +186,7 @@ public class HotEntryCache { final CompletableFuture future = this.future.getAcquire(); try { - final Instant minNextEvictionTime = evictStaleEntries(); + final long minNextEvictionTime = evictStaleEntries(); timeToNextEviction = normalizeDurationToNextEviction(minNextEvictionTime); @@ -194,34 +201,34 @@ public class HotEntryCache { } } - private Duration normalizeDurationToNextEviction(final Instant minNextEvictionTime) { - Duration timeToNextEviction; - if (!minNextEvictionTime.equals(Instant.MAX)) { - timeToNextEviction = minSleepPeriod; + private long normalizeDurationToNextEviction(final long minNextEvictionTime) { + long timeToNextEviction; + if (minNextEvictionTime != Long.MAX_VALUE) { + timeToNextEviction = minSleepPeriodInMs; } else { - final Instant now = Instant.now(); - timeToNextEviction = Duration.between(now, minNextEvictionTime); + final long now = System.currentTimeMillis(); + timeToNextEviction = minNextEvictionTime - now; } return timeToNextEviction; } - private Instant evictStaleEntries() { - Instant minNextEvictionTime = Instant.MAX; + private long evictStaleEntries() { + long minNextEvictionTime = Long.MAX_VALUE; final Set> caches = new HashSet<>(); synchronized (lock) { caches.addAll(weakCaches.keySet()); } for (final HotEntryCache cache : caches) { - final Instant nextEvictionTime = cache.evict(); - minNextEvictionTime = min(minNextEvictionTime, nextEvictionTime); + final long nextEvictionTime = cache.evict(); + minNextEvictionTime = Math.min(minNextEvictionTime, nextEvictionTime); } return minNextEvictionTime; } - private void sleepToNextEviction(final Duration timeToNextEviction) { + private void sleepToNextEviction(final long timeToNextEviction) { try { - final Duration timeToSleep = minDuration(timeToNextEviction, maxSleepPeriod); - final long timeToSleepMS = Math.max(timeToSleep.toMillis(), minSleepPeriod.toMillis()); + final long timeToSleep = Math.min(timeToNextEviction, maxSleepPeriodInMs); + final long timeToSleepMS = Math.max(timeToSleep, minSleepPeriodInMs); LOGGER.trace("sleeping {}ms", timeToSleepMS); TimeUnit.MILLISECONDS.sleep(timeToSleepMS); } catch (final InterruptedException e) { @@ -247,12 +254,12 @@ public class HotEntryCache { return result; } - public void setMinSleepPeriod(final Duration minSleepPeriod) { - this.minSleepPeriod = minSleepPeriod; + public void setMinSleepPeriod(final long minSleepPeriodInMs) { + this.minSleepPeriodInMs = minSleepPeriodInMs; } - public void setMaxSleepPeriod(final Duration maxSleepPeriod) { - this.maxSleepPeriod = maxSleepPeriod; + public void setMaxSleepPeriod(final long maxSleepPeriodInMs) { + this.maxSleepPeriodInMs = maxSleepPeriodInMs; } } @@ -265,7 +272,7 @@ public class HotEntryCache { TIME_UPDATER.start(); } - private static Instant now; + private static long now; /** * Mapping of the key to the value. @@ -286,7 +293,7 @@ public class HotEntryCache { this.timeToLive = timeToLive; this.clock = clock; this.name = name; - now = Instant.now(clock); + now = clock.millis(); EVICTER.addCache(this); TIME_UPDATER.addCache(this); @@ -316,20 +323,20 @@ public class HotEntryCache { listeners.add(listener); } - static void setMinSleepPeriod(final Duration minSleepPeriod) { - EVICTER.setMinSleepPeriod(minSleepPeriod); - TIME_UPDATER.setUpdateInterval(minSleepPeriod.dividedBy(2)); + static void setMinSleepPeriod(final long minSleepPeriodInMs) { + EVICTER.setMinSleepPeriod(minSleepPeriodInMs); + TIME_UPDATER.setUpdateInterval(minSleepPeriodInMs / 2); } - static void setMaxSleepPeriod(final Duration maxSleepPeriod) { - EVICTER.setMaxSleepPeriod(maxSleepPeriod); + static void setMaxSleepPeriod(final long maxSleepPeriodInMs) { + EVICTER.setMaxSleepPeriod(maxSleepPeriodInMs); } - static Duration getMinSleepPeriod() { + static long getMinSleepPeriod() { return EVICTER.getMinSleepPeriod(); } - static Duration getMaxSleepPeriod() { + static long getMaxSleepPeriod() { return EVICTER.getMaxSleepPeriod(); } @@ -354,7 +361,7 @@ public class HotEntryCache { oldEntry.setValue(value); entry = oldEntry; } else { - final Instant creationTime = now(); + final long creationTime = now(); entry = new Entry<>(value, creationTime); } touch(k, entry); @@ -387,7 +394,7 @@ public class HotEntryCache { final boolean wasEmptyBefore = cache.isEmpty(); final Entry entry = cache.computeIfAbsent(key, (k) -> { final V value = mappingFunction.apply(k); - final Instant creationTime = now(); + final long creationTime = now(); final Entry e = new Entry<>(value, creationTime); touch(key, e); return e; @@ -426,20 +433,18 @@ public class HotEntryCache { }); } - private Instant evict() { - final Instant now = now(); - final Instant oldestValuesToKeep = now.minus(timeToLive); - Instant lastAccessTime = Instant.MAX; + private long evict() { + final long now = now(); + final long oldestValuesToKeep = now - timeToLive.toMillis(); + long lastAccessTime = Long.MAX_VALUE; LOGGER.trace("{}: cache size before eviction {}", name, cache.size()); - // for (final java.util.Map.Entry> mapEntry : - // lastAccessMap.entrySet()) { for (final java.util.Map.Entry> mapEntry : cache.entrySet()) { final Entry entry = mapEntry.getValue(); - final Instant lastAccessed = entry.getLastAccessed(); - lastAccessTime = min(lastAccessTime, lastAccessed); + final long lastAccessed = entry.getLastAccessed(); + lastAccessTime = Math.min(lastAccessTime, lastAccessed); - if (lastAccessed.isAfter(oldestValuesToKeep)) { + if (lastAccessed > oldestValuesToKeep) { continue; } @@ -456,38 +461,30 @@ public class HotEntryCache { } LOGGER.trace("{}: cache size after eviction {}", name, cache.size()); - final Instant nextEvictionTime = lastAccessTime.equals(Instant.MAX) ? Instant.MAX - : lastAccessTime.plus(timeToLive); + final long nextEvictionTime = lastAccessTime == Long.MAX_VALUE ? Long.MAX_VALUE + : lastAccessTime + timeToLive.toMillis(); return nextEvictionTime; } - private static Instant min(final Instant a, final Instant b) { - return a.isBefore(b) ? a : b; - } - - private static Duration minDuration(final Duration a, final Duration b) { - return a.compareTo(b) < 0 ? a : b; - } - - private Instant now() { + private long now() { return now; } // visible for test void updateTime() { - now = Instant.now(clock); + now = clock.millis(); } private void touch(final K key, final Entry entry) { if (entry != null) { - final Instant now = now(); + final long now = now(); entry.touch(now); } } - private boolean isExpired(final Entry entry, final Instant now) { - return entry.getLastAccessed().plus(timeToLive).isBefore(now); + private boolean isExpired(final Entry entry, final long now) { + return (entry.getLastAccessed() + timeToLive.toMillis()) < now; } private void handleEvent(final K key, final V value) { diff --git a/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java b/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java index 59173e5..5e5955e 100644 --- a/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java +++ b/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java @@ -30,16 +30,16 @@ public class HotEntryCacheTest { @Test(invocationCount = 1) public void testRemovalListenerCalledOnExpire() throws InterruptedException { - final Duration originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod(); - final Duration originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod(); + final long originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod(); + final long originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod(); try { final String key = "key"; final String value = "value"; final CountDownLatch latch = new CountDownLatch(1); final HotEntryCache cache = new HotEntryCache<>(Duration.ofMillis(1), "cache-" + ++cacheId); - HotEntryCache.setMinSleepPeriod(Duration.ofMillis(1)); - HotEntryCache.setMaxSleepPeriod(Duration.ofMillis(10)); + HotEntryCache.setMinSleepPeriod(1); + HotEntryCache.setMaxSleepPeriod(10); cache.addListener((k, v) -> { Assert.assertEquals(k, key); Assert.assertEquals(v, value);