-
Notifications
You must be signed in to change notification settings - Fork 1k
[json] Optimize primitive numbers to string (50%-250% faster) #7819
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,9 @@ use std::marker::PhantomData; | |
| use crate::reader::tape::{Tape, TapeElement}; | ||
| use crate::reader::ArrayDecoder; | ||
|
|
||
| use itoa; | ||
| use ryu; | ||
|
|
||
| const TRUE: &str = "true"; | ||
| const FALSE: &str = "false"; | ||
|
|
||
|
|
@@ -85,6 +88,9 @@ impl<O: OffsetSizeTrait> ArrayDecoder for StringArrayDecoder<O> { | |
|
|
||
| let mut builder = GenericStringBuilder::<O>::with_capacity(pos.len(), data_capacity); | ||
|
|
||
| let mut float_formatter = ryu::Buffer::new(); | ||
| let mut int_formatter = itoa::Buffer::new(); | ||
|
|
||
| for p in pos { | ||
| match tape.get(*p) { | ||
| TapeElement::String(idx) => { | ||
|
|
@@ -103,20 +109,20 @@ impl<O: OffsetSizeTrait> ArrayDecoder for StringArrayDecoder<O> { | |
| TapeElement::I64(high) if coerce_primitive => match tape.get(p + 1) { | ||
| TapeElement::I32(low) => { | ||
| let val = ((high as i64) << 32) | (low as u32) as i64; | ||
| builder.append_value(val.to_string()); | ||
| builder.append_value(int_formatter.format(val)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this still copies the bytes twice -- once into the ryu buffer and then again into the StringBuilder's buffer . I suspect we could make it even faster by writing into the StringBuilder directly Note StringBuffer implements Is there some way to get ryu to write directly to that buffer? using ryu may still be faster There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok so I tested simply adding write!(builder, "{n}").unwrap();
builder.append_value("");vs builder.append_value(n.to_string());And the benches I wrote were 25% faster, so slower than itoa and ryu, but for some reason the previously written benches were all regressing by 15%: So at least that is a positive for using these libraries. For ryu writing directly to the buffer, I was not able to find a way to do this since it seems like their internal buffer is coupled to the write implementation 😢 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW saving the string allocation also likely makes a non trivial difference |
||
| } | ||
| _ => unreachable!(), | ||
| }, | ||
| TapeElement::I32(n) if coerce_primitive => { | ||
| builder.append_value(n.to_string()); | ||
| builder.append_value(int_formatter.format(n)); | ||
| } | ||
| TapeElement::F32(n) if coerce_primitive => { | ||
| builder.append_value(n.to_string()); | ||
| builder.append_value(int_formatter.format(n)); | ||
| } | ||
| TapeElement::F64(high) if coerce_primitive => match tape.get(p + 1) { | ||
| TapeElement::F32(low) => { | ||
| let val = f64::from_bits(((high as u64) << 32) | low as u64); | ||
| builder.append_value(val.to_string()); | ||
| builder.append_value(float_formatter.format_finite(val)); | ||
| } | ||
| _ => unreachable!(), | ||
| }, | ||
|
|
||
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.
I was concerned about adding these new dependencies, however, it seems serde_json already depends on
ryuanditoso this is not a net-new dependency, it is just now explicit.