Ignore:
Timestamp:
2023-08-07T22:04:55+02:00 (13 months ago)
Author:
taylor.smock
Message:

Fix #23097: Significantly reduce allocations during startup in XmlObjectParser, PluginListParser, and ReadLocalPluginInformationTask

With the Name Suggestion Index preset added to JOSM, the following methods are
relatively expensive during startup (mem old -> mem new, cpu old -> cpu new):

  • XmlObjectParser$Entry.getField (124 MB -> 8.1 MB, 501ms -> 99ms)
  • XmlObjectParser$Entry.getMethod (126 MB -> 452 kB, 292ms -> 45ms)

The gains are almost entirely from getting rid of copy calls to Method and Field
(done when calling Class.getMethods() and Class.getFields()). There are
further gains in JVM methods (like GC), but those can be a bit ticklish to
profile correctly. It does look like a 20% improvement there though
(32,653ms -> 26,075ms).

This additionally reduces pattern compilation from PluginListParser.parse and
ReadLocalPluginInformationTask.listFiles.

File:
1 edited

Legend:

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

    r16188 r18796  
    1010import java.lang.reflect.Method;
    1111import java.lang.reflect.Modifier;
    12 import java.util.Arrays;
    1312import java.util.HashMap;
    1413import java.util.Iterator;
     
    190189        private final Map<String, Field> fields = new HashMap<>();
    191190        private final Map<String, Method> methods = new HashMap<>();
     191        /** This is used to avoid array copies in {@link #getUncachedMethod(String)}. Do not modify. */
     192        private Method[] cachedKlassMethods;
     193        /** This is used to avoid array copies in {@link #getUncachedField(String)}. Do not modify. */
     194        private Field[] cachedKlassFields;
    192195
    193196        Entry(Class<?> klass, boolean onStart, boolean both) {
     
    198201
    199202        Field getField(String s) {
    200             return fields.computeIfAbsent(s, ignore -> Arrays.stream(klass.getFields())
    201                     .filter(f -> f.getName().equals(s))
    202                     .findFirst()
    203                     .orElse(null));
     203            return fields.computeIfAbsent(s, this::getUncachedField);
     204        }
     205
     206        /**
     207         * Get a field (uncached in {@link #fields})
     208         * @implNote Please profile startup when changing
     209         * @param s The field to get
     210         * @return The field, or {@code null}.
     211         */
     212        private Field getUncachedField(String s) {
     213            if (this.cachedKlassFields == null) {
     214                this.cachedKlassFields = klass.getFields();
     215            }
     216            for (Field field : this.cachedKlassFields) {
     217                if (field.getName().equals(s)) {
     218                    return field;
     219                }
     220            }
     221            return null;
    204222        }
    205223
    206224        Method getMethod(String s) {
    207             return methods.computeIfAbsent(s, ignore -> Arrays.stream(klass.getMethods())
    208                     .filter(m -> m.getName().equals(s) && m.getParameterTypes().length == 1)
    209                     .findFirst()
    210                     .orElse(null));
     225            return methods.computeIfAbsent(s, this::getUncachedMethod);
     226        }
     227
     228        /**
     229         * Get an uncached method (in {@link #methods})
     230         * @implNote Please profile startup when changing
     231         * @param s The method to find
     232         * @return The method or {@code null}.
     233         */
     234        private Method getUncachedMethod(String s) {
     235            if (cachedKlassMethods == null) {
     236                cachedKlassMethods = klass.getMethods();
     237            }
     238            for (Method method : cachedKlassMethods) {
     239                if (method.getParameterCount() == 1 && method.getName().equals(s)) {
     240                    return method;
     241                }
     242            }
     243            return null;
    211244        }
    212245    }
Note: See TracChangeset for help on using the changeset viewer.