Skip to content

Commit

Permalink
fast float/double writer option (#749)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjfanning committed Jun 22, 2022
1 parent 011e31f commit 84dd12a
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 35 deletions.
9 changes: 9 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,15 @@ public enum Feature {
* @since 2.5
*/
IGNORE_UNKNOWN(false),

/**
* Feature that determines whether to use standard Java code to write floats/doubles (default) or
* use the Schubfach algorithm which is faster. The latter approach may lead to small
* differences in the precision of the float/double that is written to the JSON output.
*
* @since 2.14
*/
USE_FAST_DOUBLE_WRITER(false)
;

private final boolean _defaultState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ public enum StreamWriteFeature
* property will result in a {@link JsonProcessingException}
*/
IGNORE_UNKNOWN(JsonGenerator.Feature.IGNORE_UNKNOWN),

/**
* Feature that determines whether to use standard Java code to write floats/doubles (default) or
* use the Schubfach algorithm which is faster. The latter approach may lead to small
* differences in the precision of the float/double that is written to the JSON output.
*
* @since 2.14
*/
USE_FAST_DOUBLE_WRITER(JsonGenerator.Feature.USE_FAST_DOUBLE_WRITER)
;

/**
Expand Down
41 changes: 36 additions & 5 deletions src/main/java/com/fasterxml/jackson/core/io/NumberOutput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.fasterxml.jackson.core.io;

import com.fasterxml.jackson.core.io.schubfach.DoubleToDecimal;
import com.fasterxml.jackson.core.io.schubfach.FloatToDecimal;

public final class NumberOutput
{
private static int MILLION = 1000000;
Expand Down Expand Up @@ -273,13 +276,41 @@ public static String toString(long v) {
return Long.toString(v);
}

public static String toString(double v) {
return Double.toString(v);
/**
* @param v double
* @return double as a string
*/
public static String toString(final double v) {
return toString(v, false);
}

/**
* @param v double
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
* @return double as a string
* @since 2.14
*/
public static String toString(final double v, final boolean useFastWriter) {
return useFastWriter ? DoubleToDecimal.toString(v) : Double.toString(v);
}

// @since 2.6
public static String toString(float v) {
return Float.toString(v);
/**
* @param v float
* @return float as a string
* @since 2.6
*/
public static String toString(final float v) {
return toString(v, false);
}

/**
* @param v float
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
* @return float as a string
* @since 2.14
*/
public static String toString(final float v, final boolean useFastWriter) {
return useFastWriter ? FloatToDecimal.toString(v) : Float.toString(v);
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1027,12 +1027,12 @@ public void writeNumber(double d) throws IOException
if (_cfgNumbersAsStrings ||
(NumberOutput.notFinite(d)
&& Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
writeString(String.valueOf(d));
writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
return;
}
// What is the max length for doubles? 40 chars?
_verifyValueWrite(WRITE_NUMBER);
writeRaw(String.valueOf(d));
writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
}

@SuppressWarnings("deprecation")
Expand All @@ -1042,12 +1042,12 @@ public void writeNumber(float f) throws IOException
if (_cfgNumbersAsStrings ||
(NumberOutput.notFinite(f)
&& Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
writeString(String.valueOf(f));
writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
return;
}
// What is the max length for floats?
_verifyValueWrite(WRITE_NUMBER);
writeRaw(String.valueOf(f));
writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,12 +801,12 @@ public void writeNumber(double d) throws IOException
{
if (_cfgNumbersAsStrings ||
(NumberOutput.notFinite(d) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) {
writeString(String.valueOf(d));
writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
return;
}
// What is the max length for doubles? 40 chars?
_verifyValueWrite(WRITE_NUMBER);
writeRaw(String.valueOf(d));
writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
}

@SuppressWarnings("deprecation")
Expand All @@ -815,12 +815,12 @@ public void writeNumber(float f) throws IOException
{
if (_cfgNumbersAsStrings ||
(NumberOutput.notFinite(f) && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS))) {
writeString(String.valueOf(f));
writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
return;
}
// What is the max length for floats?
_verifyValueWrite(WRITE_NUMBER);
writeRaw(String.valueOf(f));
writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
public class ArrayGenerationTest extends BaseTest
{
private final JsonFactory FACTORY = new JsonFactory();


protected JsonFactory jsonFactory() {
return FACTORY;
}

public void testIntArray() throws Exception
{
_testIntArray(false);
Expand Down Expand Up @@ -124,8 +128,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th
StringWriter sw = new StringWriter();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();

JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
: FACTORY.createGenerator(sw);
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
: jsonFactory().createGenerator(sw);

gen.writeArray(values, pre, elements);
gen.close();
Expand All @@ -137,8 +141,8 @@ private void _testIntArray(boolean useBytes, int elements, int pre, int post) th
json = sw.toString();
}

JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
: FACTORY.createParser(json);
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
: jsonFactory().createParser(json);
assertToken(JsonToken.START_ARRAY, p.nextToken());
for (int i = 0; i < elements; ++i) {
if ((i & 1) == 0) { // alternate
Expand All @@ -165,8 +169,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t
StringWriter sw = new StringWriter();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();

JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
: FACTORY.createGenerator(sw);
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
: jsonFactory().createGenerator(sw);

gen.writeArray(values, pre, elements);
gen.close();
Expand All @@ -178,8 +182,8 @@ private void _testLongArray(boolean useBytes, int elements, int pre, int post) t
json = sw.toString();
}

JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
: FACTORY.createParser(json);
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
: jsonFactory().createParser(json);
assertToken(JsonToken.START_ARRAY, p.nextToken());
for (int i = 0; i < elements; ++i) {
if ((i & 1) == 0) { // alternate
Expand All @@ -206,8 +210,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post)
StringWriter sw = new StringWriter();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();

JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
: FACTORY.createGenerator(sw);
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
: jsonFactory().createGenerator(sw);

gen.writeArray(values, pre, elements);
gen.close();
Expand All @@ -219,8 +223,8 @@ private void _testDoubleArray(boolean useBytes, int elements, int pre, int post)
json = sw.toString();
}

JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
: FACTORY.createParser(json);
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
: jsonFactory().createParser(json);
assertToken(JsonToken.START_ARRAY, p.nextToken());
for (int i = 0; i < elements; ++i) {
JsonToken t = p.nextToken();
Expand Down Expand Up @@ -248,8 +252,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post)
StringWriter sw = new StringWriter();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();

JsonGenerator gen = useBytes ? FACTORY.createGenerator(bytes)
: FACTORY.createGenerator(sw);
JsonGenerator gen = useBytes ? jsonFactory().createGenerator(bytes)
: jsonFactory().createGenerator(sw);

gen.writeArray(values, pre, elements);
gen.close();
Expand All @@ -261,8 +265,8 @@ private void _testStringArray(boolean useBytes, int elements, int pre, int post)
json = sw.toString();
}

JsonParser p = useBytes ? FACTORY.createParser(bytes.toByteArray())
: FACTORY.createParser(json);
JsonParser p = useBytes ? jsonFactory().createParser(bytes.toByteArray())
: jsonFactory().createParser(json);
assertToken(JsonToken.START_ARRAY, p.nextToken());
for (int i = 0; i < elements; ++i) {
JsonToken t = p.nextToken();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.fasterxml.jackson.core.write;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamWriteFeature;

public class FastDoubleArrayGenerationTest extends ArrayGenerationTest {
private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build();

protected JsonFactory jsonFactory() {
return FACTORY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.fasterxml.jackson.core.write;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamWriteFeature;

public class FastDoubleObjectWriteTest extends ObjectWriteTest {
private final JsonFactory FACTORY = JsonFactory.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build();

protected JsonFactory jsonFactory() {
return FACTORY;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
public class ObjectWriteTest
extends BaseTest
{
private final JsonFactory FACTORY = new JsonFactory();

protected JsonFactory jsonFactory() {
return FACTORY;
}

public void testEmptyObjectWrite()
throws Exception
{
StringWriter sw = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(sw);
JsonGenerator gen = jsonFactory().createGenerator(sw);

JsonStreamContext ctxt = gen.getOutputContext();
assertTrue(ctxt.inRoot());
Expand Down Expand Up @@ -59,7 +65,7 @@ public void testInvalidObjectWrite()
throws Exception
{
StringWriter sw = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(sw);
JsonGenerator gen = jsonFactory().createGenerator(sw);
gen.writeStartObject();
// Mismatch:
try {
Expand All @@ -75,7 +81,7 @@ public void testSimpleObjectWrite()
throws Exception
{
StringWriter sw = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(sw);
JsonGenerator gen = jsonFactory().createGenerator(sw);
gen.writeStartObject();
gen.writeFieldName("first");
gen.writeNumber(-901);
Expand Down Expand Up @@ -111,7 +117,7 @@ public void testConvenienceMethods()
throws Exception
{
StringWriter sw = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(sw);
JsonGenerator gen = jsonFactory().createGenerator(sw);
gen.writeStartObject();

final String TEXT = "\"some\nString!\"";
Expand Down Expand Up @@ -219,7 +225,7 @@ public void testConvenienceMethodsWithNulls()
throws Exception
{
StringWriter sw = new StringWriter();
JsonGenerator gen = new JsonFactory().createGenerator(sw);
JsonGenerator gen = jsonFactory().createGenerator(sw);
gen.writeStartObject();

gen.writeStringField("str", null);
Expand Down

0 comments on commit 84dd12a

Please sign in to comment.