-
Notifications
You must be signed in to change notification settings - Fork 90
using this for small JSON's #198
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
Comments
Here is a very bad bench but the differences are big enough...
|
That's a bit complicated to answer - one of those "it depends" situations 😭 simd gets 'better' for medium and larger files, for extremely small ones it's quite bad (i.e. smaller then the registers) I don't think that's the case for you, but there is some overhead. So first of all, for small data serde-json can absolutely be faster then simd-json! That said there are a few things: The biggest issue in the benchmark is that it's comparing struct deserialization DOM serialization. The DOM serialization is quite a bit slower. To make a fair comparison and one that makes sense for users, you have to either compare dom deserialization for both or struct deserialization for both. For benchmarks like that it usually is good to use a benchmark library as the compiler sometimes optimizes things away when it notices it isn't used. For example, the black_box function in criterion is one of those ways. (not sure if that applies here but for a good measurement it's a nice tool) The third thing that will make a difference is using
Next, and this depends a bit on your use-case, is you can optimize this by pre-allocating and re-using buffers. If your program starts, reads a small JSON, and closes again it won't help but if it is long-running this might do you good: let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
let mut string_buffer = Vec::with_capacity(2048);
let mut input_buffer = simd_json::AlignedBuf::with_capacity(1024);
for _ in 0..100 {
let p2= Person::from_slice_with_buffers(&mut json_bytes_2, &mut input_buffer, &mut string_buffer).unwrap();
} Last but not least, and again this depends on your use case, you could avoid allocating strings as simd-json is quite good at borrowing when deserialization structs (this works with serde too I think so I'll add the serde related code in this example after all got to compare apples and apples :) !): struct Person<'ser> {
#[serde(borrow)]
id: &'ser str,
index: i32,
#[serde(borrow)]
guid: &'ser str,
isActive: bool,
#[serde(borrow)]
picture: &'ser str,
age: u32
} |
Also I noticed you're using |
So I updated your benchmark a bit: #![allow(warnings)]
use std::time::Instant;
use serde::Deserialize;
use simd_json_derive::Deserialize as SimdDeserialize;
use serde_json;
use simd_json;
#[derive(Deserialize, SimdDeserialize)]
struct Person {
id: String,
index: i32,
guid: String,
isActive: bool,
picture: String,
age: u32
}
#[derive(Deserialize, SimdDeserialize)]
struct PersonBorrowed<'ser> {
#[serde(borrow)]
id: &'ser str,
index: i32,
#[serde(borrow)]
guid: &'ser str,
isActive: bool,
#[serde(borrow)]
picture: &'ser str,
age: u32
}
const N: usize = 100000;
fn main() {
let json_bytes = br#"{
"id": "60a6965e5e47ef8456878326",
"index": 0,
"guid": "cfce331d-07f3-40d3-b3d9-0672f651c26d",
"isActive": true,
"picture": "http://placehold.it/32x32",
"age": 22
}"#.to_vec();
let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
for _ in 0..N {
let p2: simd_json::OwnedValue = simd_json::to_owned_value(&mut json_bytes_2).unwrap();
}
println!("simd_json {:?}", now_2.elapsed());
let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
for _ in 0..N {
let p2: Person = simd_json::serde::from_slice(&mut json_bytes_2).unwrap();
criterion::black_box(p2);
}
println!("simd_json (struct) {:?}", now_2.elapsed());
let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
for _ in 0..N {
let p2 = Person::from_slice(&mut json_bytes_2).unwrap();
criterion::black_box(p2);
}
println!("simd_json (simd-struct) {:?}", now_2.elapsed());
let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
for _ in 0..N {
let p2 = PersonBorrowed::from_slice(&mut json_bytes_2).unwrap();
criterion::black_box(p2);
}
println!("simd_json (simd-struct borrowed) {:?}", now_2.elapsed());
let mut json_bytes_2 = json_bytes.clone();
let now_2 = Instant::now();
let mut string_buffer = Vec::with_capacity(2048);
let mut input_buffer = simd_json::AlignedBuf::with_capacity(1024);
for _ in 0..N {
let p2 = PersonBorrowed::from_slice_with_buffers(&mut json_bytes_2, &mut input_buffer, &mut string_buffer).unwrap();
criterion::black_box(p2);
}
println!("simd_json (simd-struct borrowed buffered) {:?}", now_2.elapsed());
let mut json_bytes_1 = json_bytes.clone();
let now_1 = Instant::now();
for _ in 0..N {
let p: Person = serde_json::from_slice(&json_bytes_1).unwrap();
criterion::black_box(p);
}
println!("serde {:?}", now_1.elapsed());
let mut json_bytes_1 = json_bytes.clone();
let now_1 = Instant::now();
for _ in 0..N {
let p: PersonBorrowed = serde_json::from_slice(&json_bytes_1).unwrap();
criterion::black_box(p);
}
println!("serde (borrowed) {:?}", now_1.elapsed());
} [package]
name = "simd-bench-why"
version = "0.1.0"
authors = ["Heinz N. Gies <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "*", features = ["derive"] }
serde_json = "*"
simd-json = { version = "*" }
simd-json-derive = "*"
criterion = "*" I would recommend running that to look at your local system but here are the results I get on a laptop so variance is quite high bit serde is constantly faster:
|
wow, thanks for the detailed response! I ran your updated benchmark:
I actually had no idea you could use str slices for string fields with serde that could definitely speed up my program. Thanks! |
👍 so the bottom line looks like this is a case where serde is faster :) just for giggles, I'd recommend giving it a spin in the app, switching between simd / serde on a feature flag is fairly simple given that they both have derive mechanics. I won't expect this to change but still curious :D also if you're looking at processing newline delimited JSON, #194 might be something for you to keep an eye out for, if we get to implementing that the negative effects of small JSON for newline delimited readers will be negated. |
on modern cpus simd seems faster using structs and borrowed
with N= 10M instead of 100k
|
That's a cool insight thank you! |
There have been a number of updates to simd-json's performance with 0.13 simd-json is now significantly faster when taking full advantage of it:
I'll close this for now |
In case other people stumble into this, Zen4 performs like this:
|
are you running that with release builds and native CPU compilation? Those numbers are all surprisingly high. |
Right, with
|
Ah yes, that looks much more in line with what people have seen before :) |
Hi, I was benchmarking this against a very simple small JSON
Now my use case is: parse a small JSON as fast as possible just ONCE.
the results for me were (1 parse):
serde_json = 3 microseconds
simd_json = 10 microseconds
I was wondering if its normal for serde_json to be faster in smaller JSON's or am I getting incorrect results?
The text was updated successfully, but these errors were encountered: