Changeset 19075 in josm for trunk


Ignore:
Timestamp:
2024-05-07T23:47:31+02:00 (6 months ago)
Author:
taylor.smock
Message:

Fix #23628: Decrease cost of ImageWarp#warp

This decreases overall memory allocations for ImageWarp#warp by ~80% and
decreases GC by ~80%.

This does not decrease the direct CPU cost of ImageWarp#warp.

Location:
trunk/src/org/openstreetmap/josm
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java

    r18244 r19075  
    6666    /**
    6767     * Get the scale that was used for reprojecting the tile.
    68      *
     68     * <p>
    6969     * This is not necessarily the mapview scale, but may be
    7070     * adjusted to avoid excessively large cache image.
     
    7878     * Check if it is necessary to refresh the cache to match the current mapview
    7979     * scale and get optimized image quality.
    80      *
     80     * <p>
    8181     * When the maximum zoom is exceeded, this method will generally return false.
    8282     * @param currentScale the current mapview scale
     
    161161        ProjectionBounds pbTargetAligned = pbMarginAndAlign(pbTarget, scale, margin);
    162162
    163         ImageWarp.PointTransform pointTransform = pt -> {
    164             EastNorth target = new EastNorth(pbTargetAligned.minEast + pt.getX() * scale,
    165                     pbTargetAligned.maxNorth - pt.getY() * scale);
     163        ImageWarp.PointTransform pointTransform = (x, y) -> {
     164            EastNorth target = new EastNorth(pbTargetAligned.minEast + x * scale,
     165                    pbTargetAligned.maxNorth - y * scale);
    166166            EastNorth sourceEN = projServer.latlon2eastNorth(projCurrent.eastNorth2latlon(target));
    167             double x = source.getTileSize() *
     167            double x2 = source.getTileSize() *
    168168                    (sourceEN.east() - pbServer.minEast) / (pbServer.maxEast - pbServer.minEast);
    169             double y = source.getTileSize() *
     169            double y2 = source.getTileSize() *
    170170                    (pbServer.maxNorth - sourceEN.north()) / (pbServer.maxNorth - pbServer.minNorth);
    171             return new Point2D.Double(x, y);
     171            return new Point2D.Double(x2, y2);
    172172        };
    173173
     
    225225    /**
    226226     * Make sure, the image is not scaled up too much.
    227      *
     227     * <p>
    228228     * This would not give any significant improvement in image quality and may
    229229     * exceed the user's memory. The correction factor is a power of 2.
  • trunk/src/org/openstreetmap/josm/tools/ImageWarp.java

    r14816 r19075  
    66import java.awt.geom.Rectangle2D;
    77import java.awt.image.BufferedImage;
     8import java.awt.image.DataBuffer;
    89import java.util.HashMap;
    910import java.util.HashSet;
     
    1415/**
    1516 * Image warping algorithm.
    16  *
     17 * <p>
    1718 * Deforms an image geometrically according to a given transformation formula.
    1819 * @since 11858
     
    2728     * Transformation that translates the pixel coordinates.
    2829     */
     30    @FunctionalInterface
    2931    public interface PointTransform {
    3032        /**
    3133         * Translates pixel coordinates.
    32          * @param pt pixel coordinates
     34         * @param x The x coordinate
     35         * @param y The y coordinate
    3336         * @return transformed pixel coordinates
    3437         */
    35         Point2D transform(Point2D pt);
     38        Point2D transform(double x, double y);
    3639    }
    3740
    3841    /**
    3942     * Wrapper that optimizes a given {@link ImageWarp.PointTransform}.
    40      *
     43     * <p>
    4144     * It does so by spanning a grid with certain step size. It will invoke the
    4245     * potentially expensive master transform only at those grid points and use
     
    7578
    7679        @Override
    77         public Point2D transform(Point2D pt) {
    78             int xIdx = (int) Math.floor(pt.getX() / stride);
    79             int yIdx = (int) Math.floor(pt.getY() / stride);
    80             double dx = pt.getX() / stride - xIdx;
    81             double dy = pt.getY() / stride - yIdx;
     80        public Point2D transform(double x, double y) {
     81            int xIdx = (int) Math.floor(x / stride);
     82            int yIdx = (int) Math.floor(y / stride);
     83            double dx = x / stride - xIdx;
     84            double dy = y / stride - yIdx;
    8285            Point2D value00 = getValue(xIdx, yIdx);
    8386            Point2D value01 = getValue(xIdx, yIdx + 1);
     
    9295
    9396        private Point2D getValue(int xIdx, int yIdx) {
    94             return getRow(yIdx).computeIfAbsent(xIdx, k -> trfm.transform(new Point2D.Double(xIdx * stride, yIdx * stride)));
     97            final Map<Integer, Point2D> rowMap = getRow(yIdx);
     98            // This *was* computeIfAbsent. Unfortunately, it appears that it generated a ton of memory allocations.
     99            // As in, this was ~50 GB memory allocations in a test, and converting to a non-lambda form made it 1.3GB.
     100            // The primary culprit was LambdaForm#linkToTargetMethod
     101            Point2D current = rowMap.get(xIdx);
     102            if (current == null) {
     103                current = trfm.transform(xIdx * stride, yIdx * stride);
     104                rowMap.put(xIdx, current);
     105            }
     106            return current;
    95107        }
    96108
     
    98110            cleanUp(yIdx - 3);
    99111            Map<Integer, Point2D> row = cache.get(yIdx);
     112            // Note: using computeIfAbsent will drastically increase memory allocations
    100113            if (row == null) {
    101                 row = new HashMap<>();
     114                row = new HashMap<>(256);
    102115                cache.put(yIdx, row);
    103116                if (consistencyTest) {
     
    128141        /**
    129142         * Nearest neighbor.
    130          *
     143         * <p>
    131144         * Simplest possible method. Faster, but not very good quality.
    132145         */
     
    135148        /**
    136149         * Bilinear.
    137          *
     150         * <p>
    138151         * Decent quality.
    139152         */
     
    153166        BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB);
    154167        Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight());
     168        // These arrays reduce the amount of memory allocations (getRGB and setRGB are
     169        // collectively 40% of the memory cost, 78% if LambdaForm#linkToTargetMethod is
     170        // ignored). We mostly want to decrease GC pauses here.
     171        final int[] pixel = new int[1]; // Yes, this really does decrease memory allocations with TYPE_INT_ARGB.
     172        final Object sharedArray = getSharedArray(srcImg);
    155173        for (int j = 0; j < imgTarget.getHeight(); j++) {
    156174            for (int i = 0; i < imgTarget.getWidth(); i++) {
    157                 Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j));
     175                Point2D srcCoord = invTransform.transform(i, j);
    158176                if (srcRect.contains(srcCoord)) {
    159177                    int rgba;
    160178                    switch (interpolation) {
    161179                        case NEAREST_NEIGHBOR:
    162                             rgba = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg);
     180                            rgba = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg, sharedArray);
    163181                            break;
    164182                        case BILINEAR:
     
    167185                            int y0 = (int) Math.floor(srcCoord.getY());
    168186                            double dy = srcCoord.getY() - y0;
    169                             int c00 = getColor(x0, y0, srcImg);
    170                             int c01 = getColor(x0, y0 + 1, srcImg);
    171                             int c10 = getColor(x0 + 1, y0, srcImg);
    172                             int c11 = getColor(x0 + 1, y0 + 1, srcImg);
     187                            int c00 = getColor(x0, y0, srcImg, sharedArray);
     188                            int c01 = getColor(x0, y0 + 1, srcImg, sharedArray);
     189                            int c10 = getColor(x0 + 1, y0, srcImg, sharedArray);
     190                            int c11 = getColor(x0 + 1, y0 + 1, srcImg, sharedArray);
    173191                            rgba = 0;
    174192                            // loop over color components: blue, green, red, alpha
     
    184202                            throw new AssertionError(Objects.toString(interpolation));
    185203                    }
    186                     imgTarget.setRGB(i, j, rgba);
     204                    imgTarget.getRaster().setDataElements(i, j, imgTarget.getColorModel().getDataElements(rgba, pixel));
    187205                }
    188206            }
     
    191209    }
    192210
    193     private static int getColor(int x, int y, BufferedImage img) {
     211    private static Object getSharedArray(BufferedImage srcImg) {
     212        final int numBands = srcImg.getRaster().getNumBands();
     213        // Add data types as needed (shown via profiling, look for getRGB).
     214        switch (srcImg.getRaster().getDataBuffer().getDataType()) {
     215            case DataBuffer.TYPE_BYTE:
     216                return new byte[numBands];
     217            case DataBuffer.TYPE_INT:
     218                return new int[numBands];
     219            default:
     220                return null;
     221        }
     222    }
     223
     224    private static int getColor(int x, int y, BufferedImage img, Object sharedArray) {
    194225        // border strategy: continue with the color of the outermost pixel,
    195         return img.getRGB(
    196                 Utils.clamp(x, 0, img.getWidth() - 1),
    197                 Utils.clamp(y, 0, img.getHeight() - 1));
     226        final int rx = Utils.clamp(x, 0, img.getWidth() - 1);
     227        final int ry = Utils.clamp(y, 0, img.getHeight() - 1);
     228        if (sharedArray == null) {
     229            return img.getRGB(rx, ry);
     230        }
     231        return img.getColorModel().getRGB(img.getRaster().getDataElements(rx, ry, sharedArray));
    198232    }
    199233}
Note: See TracChangeset for help on using the changeset viewer.