You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/09/15 16:47:33 UTC
[sis] 03/04: Improve caching in `ConcatenatedGridCoverage` by using soft references instead than strong references, and by making possible to share the same cache between derived coverages (e.g. converted coverages).
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 426b57744b5e2a026e3970f1ce5a964b29ea08b5
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Sep 15 17:42:18 2022 +0200
Improve caching in `ConcatenatedGridCoverage` by using soft references instead than strong references,
and by making possible to share the same cache between derived coverages (e.g. converted coverages).
---
.../org/apache/sis/internal/util/Numerics.java | 10 +-
.../org/apache/sis/internal/util/NumericsTest.java | 5 +-
.../aggregate/ConcatenatedGridCoverage.java | 182 ++++++++++++++-------
.../aggregate/ConcatenatedGridResource.java | 33 ++--
4 files changed, 151 insertions(+), 79 deletions(-)
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index fce0e2b2fc..673ec3ec4a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -149,13 +149,11 @@ public final class Numerics extends Static {
/**
* Maximal integer value which is convertible to {@code float} type without lost of precision digits.
- *
- * @since 1.1
*/
public static final int MAX_INTEGER_CONVERTIBLE_TO_FLOAT = 1 << (SIGNIFICAND_SIZE_OF_FLOAT + 1);
/**
- * Right shift to apply for a result equivalent to a division by {@Long#SIZE} (ignoring negative numbers).
+ * Right shift to apply for a result equivalent to a division by {@value Long#SIZE} (ignoring negative numbers).
* The value is {@value} so that the following relationship hold: 2⁶ = {@value Long#SIZE}.
*
* <h4>Usage</h4>
@@ -167,6 +165,12 @@ public final class Numerics extends Static {
*/
public static final int LONG_SHIFT = 6;
+ /**
+ * Right shift to apply for a result equivalent to a division by {@value Integer#SIZE} (ignoring negative numbers).
+ * This is for the same purpose as {@link #LONG_SHIFT} but applied to 32 bits integer.
+ */
+ public static final int INT_SHIFT = 5;
+
/**
* Do not allow instantiation of this class.
*/
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
index de5c9a953a..db3dedbec3 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/NumericsTest.java
@@ -34,17 +34,18 @@ import static org.junit.Assert.*;
* Tests the {@link Numerics} class.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.3
* @module
*/
@SuppressWarnings("UnnecessaryBoxing")
public final strictfp class NumericsTest extends TestCase {
/**
- * Verifies the value of {@link Numerics#LONG_SHIFT}.
+ * Verifies the value of {@link Numerics#LONG_SHIFT} and {@link Numerics#INT_SHIFT}.
*/
@Test
public void verifyMaxDimension() {
+ assertEquals(Integer.SIZE, 1 << Numerics.INT_SHIFT);
assertEquals(Long.SIZE, 1 << Numerics.LONG_SHIFT);
for (int i=350; i<400; i += 17) {
assertEquals(i / Long.SIZE, i >> Numerics.LONG_SHIFT);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index 163fd34d41..31f799efcd 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@ -28,6 +28,8 @@ import org.apache.sis.coverage.grid.DisjointExtentException;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.util.collection.Cache;
// Branch-dependent imports
import org.opengis.coverage.CannotEvaluateException;
@@ -45,47 +47,108 @@ import org.opengis.referencing.operation.TransformException;
*/
final class ConcatenatedGridCoverage extends GridCoverage {
/**
- * The slices of this coverage, in the same order than {@link #coordinatesOfSlices}.
- * Each slice is not necessarily 1 cell tick; larger slices are accepted.
- * The length of this array shall be at least 2.
- *
- * <p>Some elements in the array may be {@code null} if the coverage are lazily loaded.</p>
+ * The object for identifying indices in the {@link #slices} array.
*/
- private final GridCoverage[] slices;
+ private final GridSliceLocator locator;
/**
- * The resource from which load the coverages in the {@link #slices} array, or {@code null} if none.
- * This is non-null only if the {@linkplain #slices} are lazily loaded.
+ * Index of the first slice in {@link #locator}.
*/
- private final GridCoverageResource[] resources;
+ private final int startAt;
/**
- * The domain to request when reading a coverage from the resource.
- * This is non-null only if the {@linkplain #slices} are lazily loaded.
+ * The class in charge of loading and caching grid coverages.
+ * The same loader may be shared by many {@link ConcatenatedGridCoverage} instances.
+ * Loaders are immutable (except for the cache) and thread-safe.
*/
- private final GridGeometry request;
+ private static final class Loader {
+ /**
+ * Whether loading of grid coverages should be deferred to rendering time.
+ * This is a bit set packed as {@code long} values. A bit value of 1 means
+ * that the coverages at the corresponding index should be loaded from the
+ * {@linkplain #slices slices} at same index only when first needed.
+ *
+ * @see ConcatenatedGridResource#deferredLoading
+ * @see #isDeferred(int)
+ */
+ final int[] deferred;
- /**
- * The sample dimensions to request when loading slices from the {@linkplain #resources}.
- * This is non-null only if the {@linkplain #slices} are lazily loaded.
- */
- private final int[] ranges;
+ /**
+ * The domain to request when reading a coverage from the resource.
+ */
+ private final GridGeometry domain;
+
+ /**
+ * The sample dimensions to request when loading slices from the {@linkplain #resources}.
+ */
+ private final int[] ranges;
+
+ /**
+ * Cache of {@link GridCoverage} instances. Keys are index in the {@link #slices} array.
+ */
+ private final Cache<Integer,GridCoverage> coverages;
+
+ /**
+ * Creates a new loader.
+ *
+ * @param deferred whether loading of grid coverages should be deferred to rendering time.
+ * @param domain grid geometry to request when loading data.
+ * @param ranges bands to request when loading coverages.
+ */
+ Loader(final int[] deferred, final GridGeometry domain, final int[] ranges) {
+ this.deferred = deferred;
+ this.domain = domain;
+ this.ranges = ranges;
+ coverages = new Cache<>(15, 2, true); // Keep 2 slices by strong reference (for interpolations).
+ }
+
+ /**
+ * Returns the coverage if available in the cache, or load it immediately otherwise.
+ * This method shall be invoked only when {@code isDeferred(key) == true}.
+ * This method can be invoked from any thread.
+ *
+ * @param key index of the {@link GridCoverageResource} in the {@link #slices} array.
+ * @param source value of {@code slices[key]}, used only if data need to be loaded.
+ * @return the coverage at the given index.
+ * @throws NullPointerException if no {@link GridCoverageResource} are expected to exist.
+ * @throws DataStoreException if an error occurred while loading data from the resource.
+ */
+ final GridCoverage getOrLoad(final Integer key, final GridCoverageResource source) throws DataStoreException {
+ GridCoverage coverage = coverages.peek(key);
+ if (coverage == null) {
+ final Cache.Handler<GridCoverage> handler = coverages.lock(key);
+ try {
+ coverage = handler.peek();
+ if (coverage == null) {
+ coverage = source.read(domain, ranges);
+ }
+ } finally {
+ handler.putAndUnlock(coverage);
+ }
+ }
+ return coverage;
+ }
+ }
/**
- * Whether this grid coverage should be considered as converted.
- * This is used only if the {@linkplain #slices} are lazily loaded.
+ * The object in charge of loading and caching grid coverages, or {@code null} if none.
+ * The same loader may be shared by many {@link ConcatenatedGridCoverage} instances.
*/
- private final boolean isConverted;
+ private final Loader loader;
/**
- * The object for identifying indices in the {@link #slices} array.
+ * The slices of this coverage, in the same order than {@link GridSliceLocator#sliceLows}.
+ * Array elements shall be instances of {@link GridCoverage} or {@link GridCoverageResource}.
+ * Each slice is not necessarily 1 cell tick; larger slices are accepted.
+ * The length of this array shall be at least 2. Shall be read-only.
*/
- private final GridSliceLocator locator;
+ private final Object[] slices;
/**
- * Index of the first slice in {@link #locator}.
+ * Whether this grid coverage should be considered as converted.
+ * This is used only if the {@linkplain #slices} are lazily loaded.
*/
- private final int startAt;
+ private final boolean isConverted;
/**
* Algorithm to apply when more than one grid coverage can be found at the same grid index.
@@ -96,24 +159,21 @@ final class ConcatenatedGridCoverage extends GridCoverage {
/**
* Creates a new aggregated coverage.
*
- * @param source the concatenated resource which is creating this coverage.
- * @param domain domain of the coverage to create.
- * @param request grid geometry to request when loading data. Used only if {@code resources} is non-null.
- * @param slices grid coverages for each slice. May contain {@code null} elements is lazy loading is applied.
- * @param resources resources from which to load grid coverages, or {@code null} if none.
- * @param startAt index of the first slice in {@link #locator}.
- * @param ranges bands to request when loading coverages. Used only if {@code resources} is non-null.
+ * @param source the concatenated resource which is creating this coverage.
+ * @param domain domain of the coverage to create.
+ * @param slices each slice as instances of {@link GridCoverage} or {@link GridCoverageResource}.
+ * @param startAt index of the first slice in {@link #locator}.
+ * @param deferred whether loading of grid coverages should be deferred to rendering time, or {@code null} if none.
+ * @param request grid geometry to request when loading data. Used only if {@code slices} are lazily loaded.
+ * @param ranges bands to request when loading coverages. Used only if {@code slices} are lazily loaded.
*/
- ConcatenatedGridCoverage(final ConcatenatedGridResource source, final GridGeometry domain, final GridGeometry request,
- final GridCoverage[] slices, final GridCoverageResource[] resources, final int startAt,
- final int[] ranges)
+ ConcatenatedGridCoverage(final ConcatenatedGridResource source, final GridGeometry domain, final Object[] slices,
+ final int startAt, final int[] deferred, final GridGeometry request, final int[] ranges)
{
super(domain, source.getSampleDimensions());
+ loader = (deferred != null) ? new Loader(deferred, request, ranges) : null;
this.slices = slices;
- this.resources = resources;
this.startAt = startAt;
- this.request = request;
- this.ranges = ranges;
this.isConverted = source.isConverted;
this.locator = source.locator;
this.strategy = source.strategy;
@@ -123,20 +183,27 @@ final class ConcatenatedGridCoverage extends GridCoverage {
* Creates a new aggregated coverage for the result of a conversion from/to packed values.
* This constructor assumes that all slices use the same sample dimensions.
*/
- private ConcatenatedGridCoverage(final ConcatenatedGridCoverage source, final GridCoverage[] slices,
- final List<SampleDimension> sampleDimensions, final boolean converted)
+ private ConcatenatedGridCoverage(final ConcatenatedGridCoverage source, final Object[] slices,
+ final List<SampleDimension> sampleDimensions, final boolean converted)
{
super(source.getGridGeometry(), sampleDimensions);
this.slices = slices;
- this.resources = source.resources;
+ this.loader = source.loader;
this.startAt = source.startAt;
- this.request = source.request;
- this.ranges = source.ranges;
this.locator = source.locator;
this.strategy = source.strategy;
this.isConverted = converted;
}
+ /**
+ * Returns {@code true} if the loading the coverage at the given index is deferred.
+ * If {@code true}, then {@code slices[i]} shall be an instance of {@link GridCoverageResource}.
+ * If {@code false}, then {@code slices[i]} shall be an instance of {@link GridCoverage}.
+ */
+ private boolean isDeferred(final int i) {
+ return (loader == null) || (loader.deferred[i >>> Numerics.INT_SHIFT] & (1 << i)) != 0;
+ }
+
/**
* Returns a grid coverage that contains real values or sample values,
* depending if {@code converted} is {@code true} or {@code false} respectively.
@@ -149,13 +216,13 @@ final class ConcatenatedGridCoverage extends GridCoverage {
@Override
protected GridCoverage createConvertedValues(final boolean converted) {
boolean changed = false;
- int template = -1; // Index of a grid coverage to use as a template.
- final GridCoverage[] c = new GridCoverage[slices.length];
+ GridCoverage template = null; // Arbitrary instance to use as a template for sample dimensions.
+ final Object[] c = slices.clone();
for (int i=0; i<c.length; i++) {
- final GridCoverage source = slices[i];
- if (source != null) {
+ if (!isDeferred(i)) {
+ final GridCoverage source = (GridCoverage) c[i]; // Should never fail.
changed |= (c[i] = source.forConvertedValues(converted)) != source;
- template = i;
+ template = source;
} else {
changed |= (converted != isConverted);
}
@@ -164,8 +231,8 @@ final class ConcatenatedGridCoverage extends GridCoverage {
return this;
}
final List<SampleDimension> sampleDimensions;
- if (template >= 0) {
- sampleDimensions = c[template].getSampleDimensions();
+ if (template !=null) {
+ sampleDimensions = template.getSampleDimensions();
} else {
sampleDimensions = new ArrayList<>(getSampleDimensions());
sampleDimensions.replaceAll((b) -> b.forConvertedValues(converted));
@@ -218,8 +285,9 @@ final class ConcatenatedGridCoverage extends GridCoverage {
try {
for (int i=0; i<count; i++) {
final int j = lower + i;
- final GridCoverage slice = slices[j];
- geometries[i] = (slice != null) ? slice.getGridGeometry() : resources[j].getGridGeometry();
+ final Object slice = slices[j];
+ geometries[i] = isDeferred(j) ? ((GridCoverageResource) slice).getGridGeometry()
+ : ((GridCoverage) slice).getGridGeometry();
}
lower += strategy.apply(new GridGeometry(getGridGeometry(), extent, null), geometries);
} catch (DataStoreException | TransformException e) {
@@ -230,13 +298,15 @@ final class ConcatenatedGridCoverage extends GridCoverage {
* Argument have been validated and slice has been located.
* If the coverage has not already been loaded, load it now.
*/
- GridCoverage slice = slices[lower];
- if (slice == null) try {
- slice = resources[lower].read(request, ranges).forConvertedValues(isConverted);
- slices[lower] = slice;
+ final GridCoverage coverage;
+ final Object slice = slices[lower];
+ if (!isDeferred(lower)) {
+ coverage = (GridCoverage) slice; // Should never fail.
+ } else try {
+ coverage = loader.getOrLoad(lower, (GridCoverageResource) slice).forConvertedValues(isConverted);
} catch (DataStoreException e) {
throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, lower + startAt), e);
}
- return slice.render(locator.toSliceExtent(extent, lower));
+ return coverage.render(locator.toSliceExtent(extent, lower));
}
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
index b2ada74626..903bc8f4a5 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
@@ -80,7 +80,7 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
final boolean isConverted;
/**
- * The slices of this resource, in the same order than {@link #coordinatesOfSlices}.
+ * The slices of this resource, in the same order than {@link GridSliceLocator#sliceLows}.
* Each slice is not necessarily 1 cell tick; larger slices are accepted.
*/
private final GridCoverageResource[] slices;
@@ -142,7 +142,7 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
* @param listeners listeners of the parent resource, or {@code null} if none.
* @param domain value to be returned by {@link #getGridGeometry()}.
* @param ranges value to be returned by {@link #getSampleDimensions()}.
- * @param slices the slices of this resource, in the same order than {@code coordinatesOfSlices}.
+ * @param slices the slices of this resource, in the same order than {@link GridSliceLocator#sliceLows}.
*/
ConcatenatedGridResource(final String name,
final StoreListeners listeners,
@@ -357,10 +357,10 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
* Create arrays with only the requested range, without keeping reference to this concatenated resource,
* for allowing garbage-collection of resources outside that range.
*/
- final int count = upper - lower;
- final GridGeometry[] geometries = new GridGeometry[count];
- final GridCoverage[] coverages = new GridCoverage[count];
- GridCoverageResource[] resources = null; // Created when first needed.
+ final int count = upper - lower;
+ final Object[] coverages = new Object[count];
+ final GridGeometry[] geometries = new GridGeometry[count];
+ int[] deferred = null;
int bitx = lower >>> Numerics.LONG_SHIFT;
long mask = 1L << lower; // No need for (lower & 63) because high bits are ignored.
if (count <= 1) mask = 0; // Trick for forcing coverage loading.
@@ -375,11 +375,12 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
coverages [i] = coverage;
geometries[i] = coverage.getGridGeometry();
} else {
- if (resources == null) {
- resources = new GridCoverageResource[count];
- }
- resources [i] = slice;
+ coverages [i] = slice;
geometries[i] = slice.getGridGeometry();
+ if (deferred == null) {
+ deferred = new int[Numerics.ceilDiv(count, Integer.SIZE)];
+ }
+ deferred[i >>> Numerics.INT_SHIFT] |= (1 << i);
}
if ((mask <<= 1) == 0) {
mask=1;
@@ -387,17 +388,13 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
}
}
/*
- * If it was not necessary to keep references to any resource, clear references to information
- * which were needed only for loading coverages from resources. Then create create the coverage.
+ * Following cast should never fail because the `mask = 0` trick ensures that we loaded the coverage.
+ * Otherwise (if more than one slice), create a concatenation of all slices.
*/
if (count == 1) {
- return coverages[0];
- }
- if (resources == null) {
- domain = null;
- ranges = null;
+ return (GridCoverage) coverages[0];
}
final GridGeometry union = locator.union(gridGeometry, Arrays.asList(geometries), GridGeometry::getExtent);
- return new ConcatenatedGridCoverage(this, union, domain, coverages, resources, lower, ranges);
+ return new ConcatenatedGridCoverage(this, union, coverages, lower, deferred, domain, ranges);
}
}