Changeset 18754 in josm for trunk


Ignore:
Timestamp:
2023-06-14T00:22:08+02:00 (14 months ago)
Author:
taylor.smock
Message:

Significantly reduce allocations in GeoJSONWriter

When using josm validate -i MesaCountyCO.osm.gz, where Mesa County, CO is the
data inside Mesa County, Colorado, USA, this patch reduces overall runtime by
~75%. This is largely done by reducing the amount of resources it takes to write
errors to file. So GeoJSONWriter#write now takes ~99% fewer CPU cycles and ~97%
fewer memory allocations. Time spent in JVM specific methods (like GC) was
reduced by ~50%.

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

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/GeoJSONMapRouletteWriter.java

    r18723 r18754  
    66import java.util.stream.Stream;
    77
    8 import jakarta.json.Json;
    98import jakarta.json.JsonArray;
    109import jakarta.json.JsonArrayBuilder;
     
    4140     */
    4241    public Optional<JsonObject> write(final TestError testError) {
    43         final JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
    44         final JsonArrayBuilder featuresBuilder = Json.createArrayBuilder();
    45         final JsonObjectBuilder propertiesBuilder = Json.createObjectBuilder();
     42        final JsonObjectBuilder jsonObjectBuilder = JSON_PROVIDER.createObjectBuilder();
     43        final JsonArrayBuilder featuresBuilder = JSON_PROVIDER.createArrayBuilder();
     44        final JsonObjectBuilder propertiesBuilder = JSON_PROVIDER.createObjectBuilder();
    4645        propertiesBuilder.add("message", testError.getMessage());
    4746        Optional.ofNullable(testError.getDescription()).ifPresent(description -> propertiesBuilder.add("description", description));
     
    6261                .forEach(primitive -> super.appendPrimitive(primitive, featuresBuilder));
    6362        final JsonArray featureArray = featuresBuilder.build();
    64         final JsonArrayBuilder featuresMessageBuilder = Json.createArrayBuilder();
     63        final JsonArrayBuilder featuresMessageBuilder = JSON_PROVIDER.createArrayBuilder();
    6564        if (featureArray.isEmpty()) {
    6665            Logging.trace("Could not generate task for {0}", testError.getMessage());
     
    6867        }
    6968        JsonObject primitive = featureArray.getJsonObject(0);
    70         JsonObjectBuilder replacementPrimitive = Json.createObjectBuilder(primitive);
     69        JsonObjectBuilder replacementPrimitive = JSON_PROVIDER.createObjectBuilder(primitive);
    7170        final JsonObjectBuilder properties;
    7271        if (primitive.containsKey("properties") && primitive.get("properties").getValueType() == JsonValue.ValueType.OBJECT) {
    73             properties = Json.createObjectBuilder(primitive.getJsonObject("properties"));
     72            properties = JSON_PROVIDER.createObjectBuilder(primitive.getJsonObject("properties"));
    7473        } else {
    75             properties = Json.createObjectBuilder();
     74            properties = JSON_PROVIDER.createObjectBuilder();
    7675        }
    7776        properties.addAll(propertiesBuilder);
  • trunk/src/org/openstreetmap/josm/io/GeoJSONWriter.java

    r18723 r18754  
    2727import jakarta.json.JsonValue;
    2828import jakarta.json.JsonWriter;
     29import jakarta.json.spi.JsonProvider;
    2930import jakarta.json.stream.JsonGenerator;
    3031import jakarta.json.stream.JsonParser;
     
    7475    private static final BooleanProperty SKIP_EMPTY_NODES = new BooleanProperty("geojson.export.skip-empty-nodes", true);
    7576    private static final BooleanProperty UNTAGGED_CLOSED_IS_POLYGON = new BooleanProperty("geojson.export.untagged-closed-is-polygon", false);
     77
     78    /**
     79     * Used to avoid many calls to {@link JsonProvider#provider} in {@link #getCoorArray(JsonArrayBuilder, EastNorth)}.
     80     * For validating Mesa County, CO, this reduces CPU and memory usage of {@link #write()} by ~80%. By using this for
     81     * other {@link Json} calls, {@link #write()} takes ~95% less resources than the original. And the entire process
     82     * takes 1/4 of the time (38 minutes -&gt; <10 minutes).
     83     * <p>
     84     * For more details, see <a href="https://github.com/jakartaee/jsonp-api/issues/346">JSONP #346</a>.
     85     */
     86    protected static final JsonProvider JSON_PROVIDER = JsonProvider.provider();
    7687    private static final Set<Way> processedMultipolygonWays = new HashSet<>();
    77     private EnumSet<Options> options = EnumSet.noneOf(Options.class);
     88    private final EnumSet<Options> options = EnumSet.noneOf(Options.class);
    7889
    7990    /**
     
    138149    public void write(boolean pretty, Writer writer) {
    139150        Map<String, Object> config = Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, pretty);
    140         try (JsonWriter jsonWriter = Json.createWriterFactory(config).createWriter(writer)) {
    141             JsonObjectBuilder object = Json.createObjectBuilder()
     151        try (JsonWriter jsonWriter = JSON_PROVIDER.createWriterFactory(config).createWriter(writer)) {
     152            JsonObjectBuilder object = JSON_PROVIDER.createObjectBuilder()
    142153                    .add("type", "FeatureCollection")
    143154                    .add("generator", "JOSM");
     
    184195                if (writeAsPolygon) {
    185196                    geomObj.add("type", "Polygon");
    186                     geomObj.add("coordinates", Json.createArrayBuilder().add(array));
     197                    geomObj.add("coordinates", JSON_PROVIDER.createArrayBuilder().add(array));
    187198                } else {
    188199                    geomObj.add("type", "LineString");
     
    223234         */
    224235        private void visitMultiGeometry(final Relation r) {
    225             final JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder();
     236            final JsonArrayBuilder jsonArrayBuilder = JSON_PROVIDER.createArrayBuilder();
    226237            r.getMemberPrimitives().stream().filter(p -> !(p instanceof Relation))
    227238                    .map(p -> {
    228                         final JsonObjectBuilder tempGeomObj = Json.createObjectBuilder();
     239                        final JsonObjectBuilder tempGeomObj = JSON_PROVIDER.createObjectBuilder();
    229240                        p.accept(new GeometryPrimitiveVisitor(tempGeomObj));
    230241                        return tempGeomObj.build();
     
    239250         */
    240251        private void visitMultiPoints(final Relation r) {
    241             final JsonArrayBuilder multiPoint = Json.createArrayBuilder();
     252            final JsonArrayBuilder multiPoint = JSON_PROVIDER.createArrayBuilder();
    242253            r.getMembers().stream().map(RelationMember::getMember).filter(Node.class::isInstance).map(Node.class::cast)
    243254                    .map(latLon -> getCoorArray(null, latLon))
     
    252263         */
    253264        private void visitMultiLineString(final Relation r) {
    254             final JsonArrayBuilder multiLine = Json.createArrayBuilder();
     265            final JsonArrayBuilder multiLine = JSON_PROVIDER.createArrayBuilder();
    255266            r.getMembers().stream().map(RelationMember::getMember).filter(Way.class::isInstance).map(Way.class::cast)
    256267                    .map(Way::getNodes).map(p -> {
     
    274285                final Pair<List<MultipolygonBuilder.JoinedPolygon>, List<MultipolygonBuilder.JoinedPolygon>> mp =
    275286                        MultipolygonBuilder.joinWays(r);
    276                 final JsonArrayBuilder polygon = Json.createArrayBuilder();
     287                final JsonArrayBuilder polygon = JSON_PROVIDER.createArrayBuilder();
    277288                // Peek would theoretically be better for these two streams, but SonarLint doesn't like it.
    278289                // java:S3864: "Stream.peek" should be used with caution
     
    303314                        })
    304315                        .forEach(polygon::add);
    305                 final JsonArrayBuilder multiPolygon = Json.createArrayBuilder().add(polygon);
     316                final JsonArrayBuilder multiPolygon = JSON_PROVIDER.createArrayBuilder().add(polygon);
    306317                geomObj.add("type", "MultiPolygon");
    307318                geomObj.add("coordinates", multiPolygon);
     
    310321
    311322        private JsonArrayBuilder getCoorsArray(Iterable<Node> nodes) {
    312             final JsonArrayBuilder builder = Json.createArrayBuilder();
     323            final JsonArrayBuilder builder = JSON_PROVIDER.createArrayBuilder();
    313324            for (Node n : nodes) {
    314325                if (n.isLatLonKnown()) {
     
    325336
    326337    private static JsonArrayBuilder getCoorArray(JsonArrayBuilder builder, EastNorth c) {
    327         return (builder != null ? builder : Json.createArrayBuilder())
     338        return (builder != null ? builder : JSON_PROVIDER.createArrayBuilder())
    328339                .add(BigDecimal.valueOf(c.getX()).setScale(11, RoundingMode.HALF_UP))
    329340                .add(BigDecimal.valueOf(c.getY()).setScale(11, RoundingMode.HALF_UP));
     
    337348
    338349        // Properties
    339         final JsonObjectBuilder propObj = Json.createObjectBuilder();
     350        final JsonObjectBuilder propObj = JSON_PROVIDER.createObjectBuilder();
    340351        for (Map.Entry<String, String> t : p.getKeys().entrySet()) {
    341352            // If writing OSM information, follow Overpass syntax (escape `@` with another `@`)
     
    357368            }
    358369            if (options.contains(Options.WRITE_OSM_INFORMATION) && p.getReferrers(true).stream().anyMatch(Relation.class::isInstance)) {
    359                 final JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder();
     370                final JsonArrayBuilder jsonArrayBuilder = JSON_PROVIDER.createArrayBuilder();
    360371                for (Relation relation : Utils.filteredCollection(p.getReferrers(), Relation.class)) {
    361                     final JsonObjectBuilder relationObject = Json.createObjectBuilder();
     372                    final JsonObjectBuilder relationObject = JSON_PROVIDER.createObjectBuilder();
    362373                    relationObject.add("rel", relation.getId());
    363374                    Collection<RelationMember> members = relation.getMembersFor(Collections.singleton(p));
     
    365376                    relationObject.add("role",
    366377                            members.stream().map(RelationMember::getRole).collect(Collectors.joining(";")));
    367                     final JsonObjectBuilder relationKeys = Json.createObjectBuilder();
     378                    final JsonObjectBuilder relationKeys = JSON_PROVIDER.createObjectBuilder();
    368379                    // Uncertain if the @relation reltags need to be @ escaped. I don't think so, as example output
    369380                    // didn't have any metadata in it.
     
    379390
    380391        // Geometry
    381         final JsonObjectBuilder geomObj = Json.createObjectBuilder();
     392        final JsonObjectBuilder geomObj = JSON_PROVIDER.createObjectBuilder();
    382393        p.accept(new GeometryPrimitiveVisitor(geomObj));
    383394        final JsonObject geom = geomObj.build();
     
    385396        if (!geom.isEmpty()) {
    386397            // Build primitive JSON object
    387             array.add(Json.createObjectBuilder()
     398            array.add(JSON_PROVIDER.createObjectBuilder()
    388399                    .add("type", "Feature")
    389400                    .add("properties", prop.isEmpty() ? JsonValue.NULL : prop)
     
    394405    private static JsonValue convertValueToJson(String value) {
    395406        if (value.startsWith(JSON_VALUE_START_MARKER) && value.endsWith(JSON_VALUE_END_MARKER)) {
    396             try (JsonParser parser = Json.createParser(new StringReader(value))) {
     407            try (JsonParser parser = JSON_PROVIDER.createParser(new StringReader(value))) {
    397408                if (parser.hasNext() && parser.next() != null) {
    398409                    return parser.getValue();
     
    402413            }
    403414        }
    404         return Json.createValue(value);
     415        return JSON_PROVIDER.createValue(value);
    405416    }
    406417
     
    420431    protected void appendBounds(Bounds b, JsonObjectBuilder object) {
    421432        if (b != null) {
    422             JsonArrayBuilder builder = Json.createArrayBuilder();
     433            JsonArrayBuilder builder = JSON_PROVIDER.createArrayBuilder();
    423434            getCoorArray(builder, b.getMin());
    424435            getCoorArray(builder, b.getMax());
     
    428439
    429440    protected void appendLayerFeatures(DataSet ds, JsonObjectBuilder object) {
    430         JsonArrayBuilder array = Json.createArrayBuilder();
     441        JsonArrayBuilder array = JSON_PROVIDER.createArrayBuilder();
    431442        if (ds != null) {
    432443            processedMultipolygonWays.clear();
Note: See TracChangeset for help on using the changeset viewer.