Last active
February 11, 2026 22:09
-
-
Save hsqStephenZhang/17f1c950e9be20d838a468d955fd0f0e to your computer and use it in GitHub Desktop.
wasm simd benchmark for sonic-rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! A simple benchmark for comparing wasm fallback vs simd128 performance. | |
| //! | |
| //! Build & run: | |
| //! # fallback (no simd128) | |
| //! cargo build --example wasm_bench --target wasm32-wasip1 --release | |
| //! wasmtime run target/wasm32-wasip1/release/examples/wasm_bench.wasm | |
| //! | |
| //! # simd128 | |
| //! RUSTFLAGS="-C target-feature=+simd128" \ | |
| //! cargo build --example wasm_bench --target wasm32-wasip1 --release | |
| //! wasmtime run target/wasm32-wasip1/release/examples/wasm_bench.wasm | |
| use std::time::Instant; | |
| use sonic_rs::pointer; | |
| /// A medium-sized JSON payload (~4KB) that exercises various JSON features: | |
| /// strings, numbers, booleans, nulls, nested objects, arrays. | |
| const SAMPLE_JSON: &str = r#" | |
| { | |
| "statuses": [ | |
| { | |
| "id": 505874924095815700, | |
| "id_str": "505874924095815681", | |
| "text": "RT @yaborutweet: Oteki tweet", | |
| "source": "<a href=\"http://twitter.com/download/android\">Twitter for Android</a>", | |
| "truncated": false, | |
| "user": { | |
| "id": 2696111005, | |
| "name": "MDistribution", | |
| "screen_name": "MaboroshiDist", | |
| "location": "Japan", | |
| "description": "This is a test description that spans multiple words and has some unicode: café résumé naïve", | |
| "followers_count": 21, | |
| "friends_count": 5, | |
| "listed_count": 0, | |
| "favourites_count": 0, | |
| "statuses_count": 1, | |
| "created_at": "Mon Jul 28 12:38:12 +0000 2014", | |
| "verified": false, | |
| "lang": "ja", | |
| "profile_image_url": "http://pbs.twimg.com/profile_images/12345/image_normal.jpeg" | |
| }, | |
| "retweet_count": 4, | |
| "favorite_count": 0, | |
| "entities": { | |
| "hashtags": [], | |
| "symbols": [], | |
| "urls": [], | |
| "user_mentions": [ | |
| { | |
| "screen_name": "yaborutweet", | |
| "name": "test user", | |
| "id": 461104190, | |
| "indices": [3, 15] | |
| } | |
| ] | |
| }, | |
| "lang": "ja" | |
| }, | |
| { | |
| "id": 505874922023837700, | |
| "id_str": "505874922023837696", | |
| "text": "Some example text with special chars: \"quotes\" and \\backslash\\ and /slashes/ and tab:\there", | |
| "source": "web", | |
| "truncated": false, | |
| "user": { | |
| "id": 1234567890, | |
| "name": "Test User 2", | |
| "screen_name": "testuser2", | |
| "location": null, | |
| "description": null, | |
| "followers_count": 100, | |
| "friends_count": 200, | |
| "listed_count": 5, | |
| "favourites_count": 50, | |
| "statuses_count": 300, | |
| "created_at": "Thu Jan 01 00:00:00 +0000 2015", | |
| "verified": true, | |
| "lang": "en" | |
| }, | |
| "coordinates": { | |
| "type": "Point", | |
| "coordinates": [-73.99, 40.74] | |
| }, | |
| "retweet_count": 0, | |
| "favorite_count": 10, | |
| "entities": { | |
| "hashtags": [ | |
| {"text": "test", "indices": [0, 5]}, | |
| {"text": "benchmark", "indices": [6, 16]} | |
| ], | |
| "symbols": [], | |
| "urls": [ | |
| { | |
| "url": "http://t.co/example", | |
| "expanded_url": "http://example.com/very/long/path/to/resource", | |
| "display_url": "example.com/very/long/pa...", | |
| "indices": [17, 37] | |
| } | |
| ], | |
| "user_mentions": [] | |
| }, | |
| "lang": "en" | |
| } | |
| ], | |
| "search_metadata": { | |
| "completed_in": 0.087, | |
| "max_id": 505874924095815700, | |
| "max_id_str": "505874924095815681", | |
| "next_results": "?max_id=505874847260864511&q=%E4%B8%80&count=100&include_entities=1", | |
| "query": "%E4%B8%80", | |
| "refresh_url": "?since_id=505874924095815681&q=%E4%B8%80&include_entities=1", | |
| "count": 100, | |
| "since_id": 0, | |
| "since_id_str": "0" | |
| } | |
| } | |
| "#; | |
| fn bench_parse_to_value(json: &[u8], iterations: usize) -> std::time::Duration { | |
| let start = Instant::now(); | |
| for _ in 0..iterations { | |
| let v: sonic_rs::Value = sonic_rs::from_slice(json).unwrap(); | |
| std::hint::black_box(&v); | |
| } | |
| start.elapsed() | |
| } | |
| fn bench_get_from(json: &[u8], iterations: usize) -> std::time::Duration { | |
| let start = Instant::now(); | |
| for _ in 0..iterations { | |
| let v = sonic_rs::get(json, pointer!["statuses", 0, "user", "name"]); | |
| assert!(v.is_ok()); | |
| std::hint::black_box(&v); | |
| let v = sonic_rs::get(json, &["search_metadata", "query"]); | |
| assert!(v.is_ok()); | |
| std::hint::black_box(&v); | |
| let v = sonic_rs::get( | |
| json, | |
| pointer!["statuses", 1, "entities", "urls", 0, "expanded_url"], | |
| ); | |
| assert!(v.is_ok()); | |
| std::hint::black_box(&v); | |
| } | |
| start.elapsed() | |
| } | |
| fn bench_serialize(json: &[u8], iterations: usize) -> std::time::Duration { | |
| let v: sonic_rs::Value = sonic_rs::from_slice(json).unwrap(); | |
| let start = Instant::now(); | |
| for _ in 0..iterations { | |
| let s = sonic_rs::to_string(&v).unwrap(); | |
| std::hint::black_box(&s); | |
| } | |
| start.elapsed() | |
| } | |
| fn main() { | |
| let json = SAMPLE_JSON.trim().as_bytes(); | |
| let json_len = json.len(); | |
| // Detect SIMD mode | |
| let simd_mode = if cfg!(all(target_arch = "wasm32", target_feature = "simd128")) { | |
| "wasm-simd128" | |
| } else if cfg!(target_feature = "sse2") { | |
| "sse2" | |
| } else if cfg!(all(target_feature = "neon", target_arch = "aarch64")) { | |
| "neon" | |
| } else { | |
| "fallback (scalar)" | |
| }; | |
| println!("=== sonic-rs wasm benchmark ==="); | |
| println!("SIMD mode: {simd_mode}"); | |
| println!("JSON payload size: {json_len} bytes"); | |
| println!(); | |
| // Warmup | |
| let _ = bench_parse_to_value(json, 100); | |
| let iterations = 10_000; | |
| // 1. Parse to Value | |
| let dur = bench_parse_to_value(json, iterations); | |
| let ns_per_op = dur.as_nanos() as f64 / iterations as f64; | |
| let throughput = json_len as f64 / (ns_per_op / 1e9) / 1024.0 / 1024.0; | |
| println!("[parse_to_value] {iterations} iters, {ns_per_op:.0} ns/op, {throughput:.1} MB/s"); | |
| // 2. get_from (3 lookups per iteration) | |
| let dur = bench_get_from(json, iterations); | |
| let ns_per_op = dur.as_nanos() as f64 / iterations as f64; | |
| println!("[get_from x3] {iterations} iters, {ns_per_op:.0} ns/op"); | |
| // 3. Serialize | |
| let dur = bench_serialize(json, iterations); | |
| let ns_per_op = dur.as_nanos() as f64 / iterations as f64; | |
| let throughput = json_len as f64 / (ns_per_op / 1e9) / 1024.0 / 1024.0; | |
| println!("[serialize] {iterations} iters, {ns_per_op:.0} ns/op, {throughput:.1} MB/s"); | |
| println!(); | |
| println!("Done."); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment