-
-
Notifications
You must be signed in to change notification settings - Fork 800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize parsing 19 digit longs #865
Conversation
Avoid slow path and allocation when parsing 19 digit longs.
@@ -152,6 +152,32 @@ public static long parseLong(char[] ch, int off, int len) | |||
long val = parseInt(ch, off, len1) * L_BILLION; | |||
return val + (long) parseInt(ch, off+len1, 9); | |||
} | |||
|
|||
/** | |||
* Parses an unsigned long made up of exactly 19 digits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks wrong - the code seems to handle signed numbers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This the same approach that com.fasterxml.jackson.core.util.TextBuffer#contentsAsInt(boolean)
uses, it calls com.fasterxml.jackson.core.io.NumberInput#parseInt(char[], int, int)
that parses an optionally singed value unsigned by ignoring the sign and later flips the sign if it had a minus sign.
num = (num * 10) + (c - '0'); | ||
} | ||
return negative ? -num : num; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you provide a jmh test project to prove this is faster? you can extend https://github.com/pjfanning/jackson-number-parse-bench if you like - you obviously have the test case, just would be handy to be able to run it independently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is really such a performance drag, shouldn't there be an openjdk issue - or something similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is really such a performance drag, shouldn't there be an openjdk issue - or something similar?
Outsiders can't create OpenJDK issues. And even if, the discussions alone would probably take years. The time investment is massive with no guaranteed outcome. To give you a concrete example openjdk/jdk#6275.
Even if everything would go smoothly we would still have to wait years until Jackson updates the minimum version to one that has the fixes. Jackson currently has a base of JDK 8 which was released 8 years ago. So we're looking at a best case of 10 years.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you provide a jmh test project to prove this is faster? you can extend https://github.com/pjfanning/jackson-number-parse-bench if you like - you obviously have the test case, just would be handy to be able to run it independently
I saw there is also https://github.com/FasterXML/jackson-benchmarks let me know what you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whichever suits you. I have a vague recollection that jackson-benchmarks is configured to run the benchmarks for a long time. There was something about the project that made me go off and create my own where I could reconfigure the settings more readily.
Another thing that I'm concerned with is that with a custom long parser, we would need to have test coverage for a wide range of possible values. The FastDoubleParser that we allow use of is
|
|
Although |
Could NumberInput.parseLong be changed to call parseLong19 where appropriate - it already uses parseInt for small nums. Would it be simpler just to have parseLong parse all numbers the new way (if the performance was not worse)? |
Right now method is quite particular expecting length of 10 - 18 characters and there'd be one more check (and branch). |
There you go pjfanning/jackson-number-parse-bench#2 I had to change repositories {
mavenLocal()
}
jmh {
includes = ['org.example.jackson.bench.LongParserBench']
profilers = ['gc'] // or whatever you like best like jfr or async
} On a Ryzen 7 PRO 5750GE and JDK 17.0.5 we go from
to
We go from 21860.758 ops/s to 31230.896 ops/s. I haven yet looked at JDK 8 results, it's possible the differences are greater there due to the lack of compact strings. |
|
*/ | ||
public static long parseLong19(char[] ch, int off, boolean negative) | ||
{ | ||
// Note: caller must ensure length is [10, 18] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you remove this comment line which doesn't match this code?
Thank you @marschall -- I'll merge this. May do minor tweaks but looks good & can go in 2.15.0. |
I had a look at this, it would require a bit more changes. Currently If we we wanted to change this we would have to:
|
I think we are ok for now; if anyone really wants to optimize things further, a new PR would be fine. |
Parsing 19 digit long values is significantly slower than parsing 18 digit long values as we are hitting a slow path that does a string allocation.
19 digit long values are quite common when a random number generator is used to generate long values.
We have to pay attention that we only use this fast path if the value is a valid long. This can be achieved by calling
#inLongRange
first.In the following synthetic microbenchmark a throughput increase of about 33% with a drastic reduction in the allocation rate can be achieved.
If we have a look at the allocations before we see that it is dominated by
byte[]
allocations forString
allocationsResulting in many gcs
With the changes in this PR the
byte[]
andString
allocations are goneResulting in no more gcs