Skip to content

Commit 8b87cc1

Browse files
authored
Improve perf of float and double parsing in TextBuffer (#1230)
1 parent 65b0d6d commit 8b87cc1

File tree

4 files changed

+145
-14
lines changed

4 files changed

+145
-14
lines changed

release-notes/VERSION-2.x

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ a pure JSON library.
1616

1717
2.18.0 (not yet released)
1818

19+
#1230: Improve performance of `float` and `double` parsing from `TextBuffer`
20+
(implemented by @pjfanning)
1921
#1251: `InternCache` replace synchronized with `ReentrantLock` - the cache
2022
size limit is no longer strictly enforced for performance reasons but
2123
we should never go far about the limit
22-
(contributed by @pjfanning)
24+
(implemented by @pjfanning)
2325
#1252: `ThreadLocalBufferManager` replace synchronized with `ReentrantLock`
24-
(contributed by @pjfanning)
26+
(implemented by @pjfanning)
2527
#1257: Increase InternCache default max size from 100 to 200
26-
#1262: Add diagnostic method pooledCount() in RecyclerPool
28+
#1262: Add diagnostic method `pooledCount()` in `RecyclerPool`
2729
#1266: Change default recycler pool to `bewConcurrentDequePool()` in 2.18
2830

2931
2.17.1 (not yet released)

src/main/java/com/fasterxml/jackson/core/io/NumberInput.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,32 @@ public static double parseDouble(final String s, final boolean useFastParser) th
400400
return useFastParser ? JavaDoubleParser.parseDouble(s) : Double.parseDouble(s);
401401
}
402402

403+
/**
404+
* @param array a char array containing a number to parse
405+
* @param useFastParser whether to use {@code FastDoubleParser}
406+
* @return closest matching double
407+
* @throws NumberFormatException if value cannot be represented by a double
408+
* @since 2.18
409+
*/
410+
public static double parseDouble(final char[] array, final boolean useFastParser) throws NumberFormatException {
411+
return parseDouble(array, 0, array.length, useFastParser);
412+
}
413+
414+
/**
415+
* @param array a char array containing a number to parse
416+
* @param offset the offset to apply when parsing the number in the char array
417+
* @param len the length of the number in the char array
418+
* @param useFastParser whether to use {@code FastDoubleParser}
419+
* @return closest matching double
420+
* @throws NumberFormatException if value cannot be represented by a double
421+
* @since 2.18
422+
*/
423+
public static double parseDouble(final char[] array, final int offset,
424+
final int len, final boolean useFastParser) throws NumberFormatException {
425+
return useFastParser ? JavaDoubleParser.parseDouble(array, offset, len) :
426+
Double.parseDouble(new String(array, offset, len));
427+
}
428+
403429
/**
404430
* @param s a string representing a number to parse
405431
* @return closest matching float
@@ -428,6 +454,32 @@ public static float parseFloat(final String s, final boolean useFastParser) thro
428454
return Float.parseFloat(s);
429455
}
430456

457+
/**
458+
* @param array a char array containing a number to parse
459+
* @param useFastParser whether to use {@code FastDoubleParser}
460+
* @return closest matching float
461+
* @throws NumberFormatException if value cannot be represented by a float
462+
* @since 2.18
463+
*/
464+
public static float parseFloat(final char[] array, final boolean useFastParser) throws NumberFormatException {
465+
return parseFloat(array, 0, array.length, useFastParser);
466+
}
467+
468+
/**
469+
* @param array a char array containing a number to parse
470+
* @param offset the offset to apply when parsing the number in the char array
471+
* @param len the length of the number in the char array
472+
* @param useFastParser whether to use {@code FastDoubleParser}
473+
* @return closest matching float
474+
* @throws NumberFormatException if value cannot be represented by a float
475+
* @since 2.18
476+
*/
477+
public static float parseFloat(final char[] array, final int offset,
478+
final int len, final boolean useFastParser) throws NumberFormatException {
479+
return useFastParser ? JavaFloatParser.parseFloat(array, offset, len) :
480+
Float.parseFloat(new String(array, offset, len));
481+
}
482+
431483
/**
432484
* @param s a string representing a number to parse
433485
* @return a BigDecimal

src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,11 @@ private char[] buf(int needed)
354354
private void clearSegments()
355355
{
356356
_hasSegments = false;
357-
/* Let's start using _last_ segment from list; for one, it's
358-
* the biggest one, and it's also most likely to be cached
359-
*/
360-
/* 28-Aug-2009, tatu: Actually, the current segment should
361-
* be the biggest one, already
362-
*/
357+
// Let's start using _last_ segment from list; for one, it's
358+
// the biggest one, and it's also most likely to be cached
359+
360+
// 28-Aug-2009, tatu: Actually, the current segment should
361+
// be the biggest one, already
363362
//_currentSegment = _segments.get(_segments.size() - 1);
364363
_segments.clear();
365364
_currentSize = _segmentSize = 0;
@@ -525,15 +524,41 @@ public char[] contentsAsArray() throws IOException {
525524
/**
526525
* Convenience method for converting contents of the buffer
527526
* into a Double value.
527+
*<p>
528+
* NOTE! Caller <b>MUST</b> validate contents before calling this method,
529+
* to ensure textual version is valid JSON floating-point token -- this
530+
* method is not guaranteed to do any validation and behavior with invalid
531+
* content is not defined (either throws an exception or returns arbitrary
532+
* number).
528533
*
529534
* @param useFastParser whether to use {@code FastDoubleParser}
530535
* @return Buffered text value parsed as a {@link Double}, if possible
531536
*
532-
* @throws NumberFormatException if contents are not a valid Java number
537+
* @throws NumberFormatException may (but is not guaranteed!) be thrown
538+
* if contents are not a valid JSON floating-point number representation
533539
*
534540
* @since 2.14
535541
*/
536-
public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException {
542+
public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException
543+
{
544+
// Order in which check is somewhat arbitrary... try likeliest ones
545+
// that do not require allocation first
546+
547+
// except _resultString first since it works best with JDK (non-fast parser)
548+
if (_resultString != null) {
549+
return NumberInput.parseDouble(_resultString, useFastParser);
550+
}
551+
if (_inputStart >= 0) { // shared?
552+
return NumberInput.parseDouble(_inputBuffer, _inputStart, _inputLen, useFastParser);
553+
}
554+
if (_currentSize == 0) { // all content in current segment!
555+
return NumberInput.parseDouble(_currentSegment, 0, _currentSize, useFastParser);
556+
}
557+
if (_resultArray != null) {
558+
return NumberInput.parseDouble(_resultArray, useFastParser);
559+
}
560+
561+
// Otherwise, segmented so need to use slow path
537562
try {
538563
return NumberInput.parseDouble(contentsAsString(), useFastParser);
539564
} catch (IOException e) {
@@ -574,14 +599,41 @@ public float contentsAsFloat() throws NumberFormatException {
574599
/**
575600
* Convenience method for converting contents of the buffer
576601
* into a Float value.
602+
*<p>
603+
* NOTE! Caller <b>MUST</b> validate contents before calling this method,
604+
* to ensure textual version is valid JSON floating-point token -- this
605+
* method is not guaranteed to do any validation and behavior with invalid
606+
* content is not defined (either throws an exception or returns arbitrary
607+
* number).
577608
*
578609
* @param useFastParser whether to use {@code FastDoubleParser}
579610
* @return Buffered text value parsed as a {@link Float}, if possible
580611
*
581-
* @throws NumberFormatException if contents are not a valid Java number
612+
* @throws NumberFormatException may (but is not guaranteed!) be thrown
613+
* if contents are not a valid JSON floating-point number representation
614+
*
582615
* @since 2.14
583616
*/
584-
public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException {
617+
public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException
618+
{
619+
// Order in which check is somewhat arbitrary... try likeliest ones
620+
// that do not require allocation first
621+
622+
// except _resultString first since it works best with JDK (non-fast parser)
623+
if (_resultString != null) {
624+
return NumberInput.parseFloat(_resultString, useFastParser);
625+
}
626+
if (_inputStart >= 0) { // shared?
627+
return NumberInput.parseFloat(_inputBuffer, _inputStart, _inputLen, useFastParser);
628+
}
629+
if (_currentSize == 0) { // all content in current segment!
630+
return NumberInput.parseFloat(_currentSegment, 0, _currentSize, useFastParser);
631+
}
632+
if (_resultArray != null) {
633+
return NumberInput.parseFloat(_resultArray, useFastParser);
634+
}
635+
636+
// Otherwise, segmented so need to use slow path
585637
try {
586638
return NumberInput.parseFloat(contentsAsString(), useFastParser);
587639
} catch (IOException e) {

src/test/java/com/fasterxml/jackson/core/util/TestTextBuffer.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import org.junit.jupiter.api.Test;
44

5+
import java.io.IOException;
6+
57
import static org.junit.jupiter.api.Assertions.*;
68

79
class TestTextBuffer
8-
extends com.fasterxml.jackson.core.JUnit5TestBase
10+
extends com.fasterxml.jackson.core.JUnit5TestBase
911
{
1012
/**
1113
* Trivially simple basic test to ensure all basic append
@@ -211,4 +213,27 @@ void getSizeFinishCurrentSegmentAndResetWith() throws Exception {
211213
assertEquals(2, textBuffer.size());
212214
}
213215

216+
public void testContentsAsFloat() throws IOException {
217+
TextBuffer textBuffer = new TextBuffer(null);
218+
textBuffer.resetWithString("1.2345678");
219+
assertEquals(1.2345678f, textBuffer.contentsAsFloat(false));
220+
}
221+
222+
public void testContentsAsFloatFastParser() throws IOException {
223+
TextBuffer textBuffer = new TextBuffer(null);
224+
textBuffer.resetWithString("1.2345678");
225+
assertEquals(1.2345678f, textBuffer.contentsAsFloat(true));
226+
}
227+
228+
public void testContentsAsDouble() throws IOException {
229+
TextBuffer textBuffer = new TextBuffer(null);
230+
textBuffer.resetWithString("1.234567890123456789");
231+
assertEquals(1.234567890123456789d, textBuffer.contentsAsDouble(false));
232+
}
233+
234+
public void testContentsAsDoubleFastParser() throws IOException {
235+
TextBuffer textBuffer = new TextBuffer(null);
236+
textBuffer.resetWithString("1.234567890123456789");
237+
assertEquals(1.234567890123456789d, textBuffer.contentsAsDouble(true));
238+
}
214239
}

0 commit comments

Comments
 (0)