use codspeed_bencher_compat::{benchmark_group, benchmark_main, Bencher}; use std::hint::black_box; use std::fs::File; use std::io::Read; use jiter::{Jiter, JsonValue, LazyIndexMap, Peek}; use serde_json::Value; fn read_file(path: &str) -> String { let mut file = File::open(path).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); contents } fn jiter_value(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = json.as_bytes(); bench.iter(|| { let v = JsonValue::parse(black_box(json_data), false).unwrap(); black_box(v) }) } fn jiter_skip(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); jiter.next_skip().unwrap(); }) } fn jiter_iter_big(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); jiter.next_array().unwrap(); loop { if let Some(peek) = jiter.next_array().unwrap() { let i = jiter.known_float(peek).unwrap(); black_box(i); while let Some(peek) = jiter.array_step().unwrap() { let i = jiter.known_float(peek).unwrap(); black_box(i); } } if jiter.array_step().unwrap().is_none() { break; } } }) } fn find_string(jiter: &mut Jiter) -> String { let peek = jiter.peek().unwrap(); match peek { Peek::String => jiter.known_str().unwrap().to_string(), Peek::Array => { assert!(jiter.known_array().unwrap().is_some()); let s = find_string(jiter).to_string(); assert!(jiter.array_step().unwrap().is_none()); s } _ => panic!("Expected string or array"), } } fn jiter_iter_pass2(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); let string = find_string(&mut jiter); jiter.finish().unwrap(); black_box(string) }) } fn jiter_iter_string_array(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); jiter.next_array().unwrap(); let i = jiter.known_str().unwrap(); // record len instead of allocating the string to simulate something like constructing a PyString black_box(i.len()); while jiter.array_step().unwrap().is_some() { let i = jiter.known_str().unwrap(); black_box(i.len()); } jiter.finish().unwrap(); }) } fn jiter_iter_true_array(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); let first_peek = jiter.next_array().unwrap().unwrap(); let i = jiter.known_bool(first_peek).unwrap(); black_box(i); while let Some(peek) = jiter.array_step().unwrap() { let i = jiter.known_bool(peek).unwrap(); black_box(i); } }) } fn jiter_iter_true_object(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); if let Some(first_key) = jiter.next_object().unwrap() { let first_key = first_key.to_string(); let first_value = jiter.next_bool().unwrap(); black_box((first_key, first_value)); while let Some(key) = jiter.next_key().unwrap() { let key = key.to_string(); let value = jiter.next_bool().unwrap(); black_box((key, value)); } } }) } fn jiter_iter_ints_array(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); let first_peek = jiter.next_array().unwrap().unwrap(); let i = jiter.known_int(first_peek).unwrap(); black_box(i); while let Some(peek) = jiter.array_step().unwrap() { let i = jiter.known_int(peek).unwrap(); black_box(i); } }) } fn jiter_iter_floats_array(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); let first_peek = jiter.next_array().unwrap().unwrap(); let i = jiter.known_float(first_peek).unwrap(); black_box(i); while let Some(peek) = jiter.array_step().unwrap() { let i = jiter.known_float(peek).unwrap(); black_box(i); } }) } fn jiter_string(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let mut jiter = Jiter::new(json_data); let string = jiter.next_str().unwrap(); black_box(string); jiter.finish().unwrap(); }) } fn serde_value(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let value: Value = serde_json::from_slice(json_data).unwrap(); black_box(value); }) } fn serde_str(path: &str, bench: &mut Bencher) { let json = read_file(path); let json_data = black_box(json.as_bytes()); bench.iter(|| { let value: String = serde_json::from_slice(json_data).unwrap(); black_box(value); }) } macro_rules! test_cases { ($file_name:ident) => { paste::item! { fn [< $file_name _jiter_value >](bench: &mut Bencher) { let file_path = format!("./benches/{}.json", stringify!($file_name)); jiter_value(&file_path, bench); } fn [< $file_name _jiter_iter >](bench: &mut Bencher) { let file_name = stringify!($file_name); let file_path = format!("./benches/{}.json", file_name); if file_name == "big" { jiter_iter_big(&file_path, bench); } else if file_name == "pass2" { jiter_iter_pass2(&file_path, bench); } else if file_name == "string_array" { jiter_iter_string_array(&file_path, bench); } else if file_name == "true_array" { jiter_iter_true_array(&file_path, bench); } else if file_name == "true_object" { jiter_iter_true_object(&file_path, bench); } else if file_name == "bigints_array" { jiter_iter_ints_array(&file_path, bench); } else if file_name == "massive_ints_array" { jiter_iter_ints_array(&file_path, bench); } else if file_name == "floats_array" { jiter_iter_floats_array(&file_path, bench); } else if file_name == "x100" || file_name == "sentence" || file_name == "unicode" { jiter_string(&file_path, bench); } } fn [< $file_name _jiter_skip >](bench: &mut Bencher) { let file_path = format!("./benches/{}.json", stringify!($file_name)); jiter_skip(&file_path, bench); } fn [< $file_name _serde_value >](bench: &mut Bencher) { let file_path = format!("./benches/{}.json", stringify!($file_name)); serde_value(&file_path, bench); } } }; } // https://json.org/JSON_checker/test/pass1.json // see https://github.com/python/cpython/blob/main/Lib/test/test_json/test_pass1.py test_cases!(pass1); // this needs ./benches/generate_big.py to be called test_cases!(big); // https://json.org/JSON_checker/test/pass2.json test_cases!(pass2); test_cases!(string_array); test_cases!(true_array); test_cases!(true_object); test_cases!(bigints_array); test_cases!(massive_ints_array); test_cases!(floats_array); // from https://github.com/json-iterator/go-benchmark/blob/179abe5e3f72acce34fb5a16f3473b901fbdd6b9/ // src/github.com/json-iterator/go-benchmark/benchmark.go#L30C17-L30C29 test_cases!(medium_response); test_cases!(x100); test_cases!(sentence); test_cases!(unicode); test_cases!(short_numbers); fn string_array_jiter_value_owned(bench: &mut Bencher) { let json = read_file("./benches/string_array.json"); let json_data = json.as_bytes(); bench.iter(|| { let v = JsonValue::parse_owned(black_box(json_data), false).unwrap(); black_box(v) }) } fn medium_response_jiter_value_owned(bench: &mut Bencher) { let json = read_file("./benches/medium_response.json"); let json_data = json.as_bytes(); bench.iter(|| { let v = JsonValue::parse_owned(black_box(json_data), false).unwrap(); black_box(v) }) } fn x100_serde_iter(bench: &mut Bencher) { serde_str("./benches/x100.json", bench); } fn lazy_map_lookup(length: i64, bench: &mut Bencher) { bench.iter(|| { let mut map: LazyIndexMap = LazyIndexMap::new(); for i in 0..length { let key = i.to_string(); map.insert(key, JsonValue::Int(i)); } // best case we get the next value each time for i in 0..length { black_box(map.get(&i.to_string()).unwrap()); } }) } fn lazy_map_lookup_1_10(bench: &mut Bencher) { lazy_map_lookup(10, bench); } fn lazy_map_lookup_2_20(bench: &mut Bencher) { lazy_map_lookup(20, bench); } fn lazy_map_lookup_3_50(bench: &mut Bencher) { lazy_map_lookup(50, bench); } benchmark_group!( benches, big_jiter_iter, big_jiter_skip, big_jiter_value, big_serde_value, bigints_array_jiter_iter, bigints_array_jiter_skip, bigints_array_jiter_value, bigints_array_serde_value, floats_array_jiter_iter, floats_array_jiter_skip, floats_array_jiter_value, floats_array_serde_value, massive_ints_array_jiter_iter, massive_ints_array_jiter_skip, massive_ints_array_jiter_value, massive_ints_array_serde_value, medium_response_jiter_iter, medium_response_jiter_skip, medium_response_jiter_value, medium_response_jiter_value_owned, medium_response_serde_value, x100_jiter_iter, x100_jiter_skip, x100_jiter_value, x100_serde_iter, x100_serde_value, sentence_jiter_iter, sentence_jiter_skip, sentence_jiter_value, sentence_serde_value, unicode_jiter_iter, unicode_jiter_skip, unicode_jiter_value, unicode_serde_value, pass1_jiter_iter, pass1_jiter_skip, pass1_jiter_value, pass1_serde_value, pass2_jiter_iter, pass2_jiter_skip, pass2_jiter_value, pass2_serde_value, string_array_jiter_iter, string_array_jiter_skip, string_array_jiter_value, string_array_jiter_value_owned, string_array_serde_value, true_array_jiter_iter, true_array_jiter_skip, true_array_jiter_value, true_array_serde_value, true_object_jiter_iter, true_object_jiter_skip, true_object_jiter_value, true_object_serde_value, lazy_map_lookup_1_10, lazy_map_lookup_2_20, lazy_map_lookup_3_50, short_numbers_jiter_iter, short_numbers_jiter_skip, short_numbers_jiter_value, short_numbers_serde_value, ); benchmark_main!(benches);