Skip to content

No-allocation DoubleToDecimal / FloatToDecimal #1652

Description

@westinrm

OpenJDK25 added implementations of DoubleToDecimal and FloatToDecimal that encode directly to Latin1 bytes without any allocations: openjdk/jdk@9d20b58

If this was public API, it could be used pretty easily within UTF8JsonGenerator for some fairly massive performance benefits when serializing large numbers of floating point numbers -- something that's very common with GeoJSON.

@Override
public void writeNumber(double d) throws IOException {
    if (this._outputTail + DoubleToDecimal.MAX_CHARS >= this._outputEnd) {
        this._flushBuffer();
    }
    this._outputTail = DoubleToDecimal.LATIN1.putDecimal(this._outputBuffer, this._outputTail, d);
}

A simple benchmark that tests this change by writing a 10k element float/double array to a null output stream shows that this approach is nearly 3x as fast, even when the relevant internal utility is copied out of the JDK (removing the requirement of --add-opens).

Benchmark                                         (type)        (mode)   Mode  Cnt        Score     Error   Units
WriteFloatingPointArray.write                      FLOAT       VANILLA  thrpt    4     1934.454 ±  84.062   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm   FLOAT       VANILLA  thrpt    4   880296.792 ±   0.825    B/op
WriteFloatingPointArray.write                      FLOAT  JACKSON_FAST  thrpt    4     2116.445 ±  52.042   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm   FLOAT  JACKSON_FAST  thrpt    4   880263.591 ± 248.715    B/op
WriteFloatingPointArray.write                      FLOAT  JDK_INTERNAL  thrpt    4     3708.587 ± 491.341   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm   FLOAT  JDK_INTERNAL  thrpt    4      383.432 ±  12.262    B/op
WriteFloatingPointArray.write                      FLOAT      JDK_COPY  thrpt    4     3623.090 ± 240.293   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm   FLOAT      JDK_COPY  thrpt    4      383.387 ±  13.224    B/op
WriteFloatingPointArray.write                     DOUBLE       VANILLA  thrpt    4     1268.260 ±  39.709   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm  DOUBLE       VANILLA  thrpt    4  1040193.112 ±   0.036    B/op
WriteFloatingPointArray.write                     DOUBLE  JACKSON_FAST  thrpt    4     1532.169 ± 190.942   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm  DOUBLE  JACKSON_FAST  thrpt    4  1040192.921 ±   0.115    B/op
WriteFloatingPointArray.write                     DOUBLE  JDK_INTERNAL  thrpt    4     2788.184 ± 497.046   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm  DOUBLE  JDK_INTERNAL  thrpt    4      315.239 ±  35.377    B/op
WriteFloatingPointArray.write                     DOUBLE      JDK_COPY  thrpt    4     2704.880 ± 379.627   ops/s
WriteFloatingPointArray.write:gc.alloc.rate.norm  DOUBLE      JDK_COPY  thrpt    4      315.170 ±  34.143    B/op

Based on these results it seems like it would be valuable to optimize Jackson's copy of *ToDecimal and bring back USE_FAST_DOUBLE_WRITER for modern JDKs? I'm not positive but I don't believe the JDK's implementation of these directly into jackson-core due to licensing, and I suspect relying on --add-opens java.base/jdk.internal.math=ALL-UNNAMED is not a good approach for a general purpose library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions