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 <martin.desruisse...@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);
     }
 }

Reply via email to