Changeset 18973 in josm


Ignore:
Timestamp:
2024-02-12T18:12:15+01:00 (3 months ago)
Author:
taylor.smock
Message:

Fix #23468: Improve performance in the Validator tree window

A large number of entries in the validator tree would cause the UI to lock for
significant periods of time when switching to a layer with many errors. Most of
the time spent was in AlphanumComparator.compare.

We need to use AlphanumComparator since String.compare would improperly sort
strings with accented characters.

The performance optimizations for this patch come from the following locations:

  • Extracting string chunk comparison to its own method (which can be compiled to native code)
  • Using String.substring instead of a StringBuilder when getting a string chunk for comparison

Both of those methods may be compiled to native code, but absent code compilation,
the performance improvements are as follows (as measured using an overpass
download of Mesa County, Colorado):

  • -86.4% CPU usage (3.366s to 0.459s)
  • -99.9% memory allocations (2.37 GB to 2.07 MB)
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/tools/AlphanumComparator.java

    r13836 r18973  
    7575     */
    7676    private static String getChunk(String s, int slength, int marker) {
    77         StringBuilder chunk = new StringBuilder();
     77        final int startMarker = marker;
    7878        char c = s.charAt(marker);
    79         chunk.append(c);
    8079        marker++;
    8180        if (Character.isDigit(c)) {
     
    8584                    break;
    8685                }
    87                 chunk.append(c);
    8886                marker++;
    8987            }
     
    9492                    break;
    9593                }
    96                 chunk.append(c);
    9794                marker++;
    9895            }
    9996        }
    100         return chunk.toString();
     97        return s.substring(startMarker, marker);
     98    }
     99
     100    /**
     101     * Check if a string is ASCII only
     102     * @param string The string to check
     103     * @param stringLength The length of the string (for performance reasons)
     104     * @return {@code true} if the string only contains ascii characters
     105     */
     106    private static boolean isAscii(String string, int stringLength) {
     107        for (int i = 0; i < stringLength; i++) {
     108            char c = string.charAt(i);
     109            if (c >= 128) {
     110                return false;
     111            }
     112        }
     113        return true;
     114    }
     115
     116    /**
     117     * Compare two string chunks
     118     * @param thisChunk The first chunk to compare
     119     * @param thisChunkLength The length of the first chunk (for performance reasons)
     120     * @param thatChunk The second chunk to compare
     121     * @param thatChunkLength The length of the second chunk (for performance reasons)
     122     * @return The {@link Comparator} result
     123     */
     124    private static int compareChunk(String thisChunk, int thisChunkLength, String thatChunk, int thatChunkLength) {
     125        int result;
     126        if (Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) {
     127            // Simple chunk comparison by length.
     128            result = thisChunkLength - thatChunkLength;
     129            // If equal, the first different number counts
     130            if (result == 0) {
     131                for (int i = 0; i < thisChunkLength; i++) {
     132                    result = thisChunk.charAt(i) - thatChunk.charAt(i);
     133                    if (result != 0) {
     134                        return result;
     135                    }
     136                }
     137            }
     138        } else {
     139            // Check if both chunks are ascii only
     140            if (isAscii(thisChunk, thisChunkLength) && isAscii(thatChunk, thatChunkLength)) {
     141                return thisChunk.compareTo(thatChunk);
     142            }
     143            // Instantiate the collator
     144            Collator compareOperator = Collator.getInstance();
     145            // Compare regardless of accented letters
     146            compareOperator.setStrength(Collator.SECONDARY);
     147            result = compareOperator.compare(thisChunk, thatChunk);
     148        }
     149        return result;
    101150    }
    102151
     
    117166
    118167        while (thisMarker < s1Length && thatMarker < s2Length) {
    119             String thisChunk = getChunk(s1, s1Length, thisMarker);
    120             thisMarker += thisChunk.length();
     168            final String thisChunk = getChunk(s1, s1Length, thisMarker);
     169            final int thisChunkLength = thisChunk.length();
     170            thisMarker += thisChunkLength;
    121171
    122172            String thatChunk = getChunk(s2, s2Length, thatMarker);
    123             thatMarker += thatChunk.length();
     173            final int thatChunkLength = thatChunk.length();
     174            thatMarker += thatChunkLength;
    124175
    125176            // If both chunks contain numeric characters, sort them numerically
    126             int result;
    127             if (Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) {
    128                 // Simple chunk comparison by length.
    129                 int thisChunkLength = thisChunk.length();
    130                 result = thisChunkLength - thatChunk.length();
    131                 // If equal, the first different number counts
    132                 if (result == 0) {
    133                     for (int i = 0; i < thisChunkLength; i++) {
    134                         result = thisChunk.charAt(i) - thatChunk.charAt(i);
    135                         if (result != 0) {
    136                             return result;
    137                         }
    138                     }
    139                 }
    140             } else {
    141                 // Instantiate the collator
    142                 Collator compareOperator = Collator.getInstance();
    143                 // Compare regardless of accented letters
    144                 compareOperator.setStrength(Collator.SECONDARY);
    145                 result = compareOperator.compare(thisChunk, thatChunk);
    146             }
     177            int result = compareChunk(thisChunk, thisChunkLength, thatChunk, thatChunkLength);
    147178
    148179            if (result != 0) {
Note: See TracChangeset for help on using the changeset viewer.