Commit 802826e
authored
refactor: JSON message with less allocations (#16130)
### What does this PR try to resolve?
This was found during experimenting `-Zbuild-analysis` with ndjson.
From me tracing the code with `cargo expand`, basically there shouldn't
have any significant performance difference between `serde(flatten)` and
inlining all the fields. Here the differences between them
* flatten one calls `Serialize::serialize_map` without fields size hint
so cannot pre-allocate Vec with `Vec::with_capacity`, whereas inline
case calls `Serialize::serialize_struct` with a known length of fields.
* flatten would end up calling `Serializer::serialize_map` and line
calls `Serializer::serialize_struct`. And in serde_json serializer
`serialize_struct` actually call `serailze_map`. So no difference on
serializer side.
* There might be some function calls not inlined I like
`FlatMapSerializer` but I doubt it is costly than allocation.
<details><summary>
Here is the `cargo-expand`'d result:
</summary>
<p>
```rust
#[derive(Serialize)]
pub struct Foo<D: Serialize> {
id: u8,
#[serde(flatten)]
data: D,
}
#[derive(Serialize)]
struct Bar {
a: bool,
}
// Expand to
extern crate serde as _serde;
impl<D: Serialize> _serde::Serialize for Foo<D>
where
D: _serde::Serialize,
{
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private228::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_map(
__serializer,
_serde::__private228::None,
)?;
_serde::ser::SerializeMap::serialize_entry(
&mut __serde_state,
"id",
&self.id,
)?;
_serde::Serialize::serialize(
&&self.data,
_serde::__private228::ser::FlatMapSerializer(&mut __serde_state),
)?;
_serde::ser::SerializeMap::end(__serde_state)
}
}
impl _serde::Serialize for Bar {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private228::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"Bar",
false as usize + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"a",
&self.a,
)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
}
```
```rust
#[derive(Serialize)]
pub struct Foo<D: Serialize> {
id: u8,
a: bool,
}
// Expand to
impl<D: Serialize> _serde::Serialize for Foo<D> {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private228::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"Foo",
false as usize + 1 + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"id",
&self.id,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"a",
&self.a,
)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
}
```
</p>
</details>
### How to test and review this PR?
CI passing.
One change is that `reason` will no longer be the first field. We could
activate the `preserve_order` feature in `serde_json` if we want, though
that may get more perf loess than the gain.
See the benchmark result in
<#16130 (comment)>1 file changed
+12
-5
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
19 | 26 | | |
20 | 27 | | |
21 | 28 | | |
| |||
0 commit comments