diff --git a/data-store/src/main/java/org/lucares/pdb/datastore/internal/DateIndexExtension.java b/data-store/src/main/java/org/lucares/pdb/datastore/internal/DateIndexExtension.java index 1d5b7dc..6611330 100644 --- a/data-store/src/main/java/org/lucares/pdb/datastore/internal/DateIndexExtension.java +++ b/data-store/src/main/java/org/lucares/pdb/datastore/internal/DateIndexExtension.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicReference; import org.lucares.pdb.api.DateTimeRange; @@ -26,6 +27,8 @@ public class DateIndexExtension { // visible for test static final ConcurrentNavigableMap DATE_PREFIX_CACHE = new ConcurrentSkipListMap<>(); + private static final AtomicReference LAST_ACCESSED = new AtomicReference<>(null); + static Set toDateIndexPrefix(final DateTimeRange dateRange) { final Set result = new TreeSet<>(); @@ -46,18 +49,24 @@ public class DateIndexExtension { } public static ParititionId toPartitionId(final long epochMilli) { - // TODO most calls will be for a similar date -> add a shortcut - final Entry value = DATE_PREFIX_CACHE.floorEntry(epochMilli); - String result; - if (value == null || !value.getValue().contains(epochMilli)) { - final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli); - DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue); - result = newValue.getDatePrefix(); + final DatePrefixAndRange lastAccessed = LAST_ACCESSED.get(); + if (lastAccessed != null && lastAccessed.getMinEpochMilli() <= epochMilli + && lastAccessed.getMaxEpochMilli() >= epochMilli) { + result = lastAccessed.getDatePrefix(); } else { - result = value.getValue().getDatePrefix(); - } + final Entry value = DATE_PREFIX_CACHE.floorEntry(epochMilli); + if (value == null || !value.getValue().contains(epochMilli)) { + final DatePrefixAndRange newValue = toDatePrefixAndRange(epochMilli); + DATE_PREFIX_CACHE.put(newValue.getMinEpochMilli(), newValue); + result = newValue.getDatePrefix(); + LAST_ACCESSED.set(newValue); + } else { + result = value.getValue().getDatePrefix(); + LAST_ACCESSED.set(value.getValue()); + } + } return new ParititionId(result); } diff --git a/data-store/src/test/java/org/lucares/pdb/datastore/internal/DateIndexExtensionTest.java b/data-store/src/test/java/org/lucares/pdb/datastore/internal/DateIndexExtensionTest.java index 34b80f6..25dc704 100644 --- a/data-store/src/test/java/org/lucares/pdb/datastore/internal/DateIndexExtensionTest.java +++ b/data-store/src/test/java/org/lucares/pdb/datastore/internal/DateIndexExtensionTest.java @@ -4,6 +4,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; +import java.util.DoubleSummaryStatistics; import java.util.List; import java.util.Set; @@ -79,11 +80,13 @@ public class DateIndexExtensionTest { final DateTimeRange range_201712_201801 = new DateTimeRange(mid_201712, min_201801); final DateTimeRange range_201712_201712 = new DateTimeRange(mid_201712, mid_201712); - final List dateIndexPrefixesWithEmptyCache = DateIndexExtension.toPartitionIds(range_201712_201802); + final List dateIndexPrefixesWithEmptyCache = DateIndexExtension + .toPartitionIds(range_201712_201802); Assert.assertEquals(dateIndexPrefixesWithEmptyCache, Arrays.asList(new ParititionId("201712"), new ParititionId("201801"), new ParititionId("201802"))); - final List dateIndexPrefixesWithFilledCache = DateIndexExtension.toPartitionIds(range_201712_201801); + final List dateIndexPrefixesWithFilledCache = DateIndexExtension + .toPartitionIds(range_201712_201801); Assert.assertEquals(dateIndexPrefixesWithFilledCache, Arrays.asList(new ParititionId("201712"), new ParititionId("201801"))); @@ -103,4 +106,39 @@ public class DateIndexExtensionTest { .toDateIndexEpochMillis(new DateTimeRange(mid_201712, min_201802)); Assert.assertEquals(dateIndexEpochMillis, Arrays.asList(exp_201712, exp_201801, exp_201802)); } + + public void testPerformance() { + + final long min = OffsetDateTime.of(2010, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); + final long mid = OffsetDateTime.of(2020, 6, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); + final long max = OffsetDateTime.of(2030, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); + + final int iterations = 1_000_000; + final int factor = 1; + final int warmup = 20 * factor; + final int rounds = warmup + 20; + + // fill the cache + DateIndexExtension.DATE_PREFIX_CACHE.clear(); + for (long i = min; i < max; i += 3600 * 24 * 28) { + DateIndexExtension.toPartitionId(i); + } + + final List measurements = new ArrayList<>(); + + for (int r = 0; r < rounds; r++) { + + final long start = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + DateIndexExtension.toPartitionId(mid); + } + final double duration = (System.nanoTime() - start) / 1_000_000.0; + System.out.println("duration: " + duration + "ms"); + measurements.add(duration); + } + + final DoubleSummaryStatistics stats = measurements.subList(warmup, rounds).stream().mapToDouble(d -> factor * d) + .summaryStatistics(); + System.out.println(stats); + } }