From 56eab93fd2055299feff53762b85ca1c6d913c08 Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 2 Feb 2024 17:47:28 +0100 Subject: [PATCH 1/8] rename benchmark because its name doesn't match --- serde_arrow/Cargo.toml | 2 +- serde_arrow/benches/{arrow2.rs => serde_arrow_bench.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename serde_arrow/benches/{arrow2.rs => serde_arrow_bench.rs} (100%) diff --git a/serde_arrow/Cargo.toml b/serde_arrow/Cargo.toml index 7699bc25..d8a304b6 100644 --- a/serde_arrow/Cargo.toml +++ b/serde_arrow/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" bench = false [[bench]] -name = "arrow2" +name = "serde_arrow_bench" # arrow-version:replace: required-features = ["arrow2-0-17", "arrow-{version}"] required-features = ["arrow2-0-17", "arrow-50"] harness = false diff --git a/serde_arrow/benches/arrow2.rs b/serde_arrow/benches/serde_arrow_bench.rs similarity index 100% rename from serde_arrow/benches/arrow2.rs rename to serde_arrow/benches/serde_arrow_bench.rs From af8820ed22a853388d3d27198ec900d579a77241 Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 2 Feb 2024 20:31:15 +0100 Subject: [PATCH 2/8] implement JSON transcoding benchmarks --- Cargo.lock | 10 ++ serde_arrow/Cargo.toml | 1 + serde_arrow/benches/groups/complex_common.rs | 4 +- serde_arrow/benches/groups/json_to_arrow.rs | 101 +++++++++++++++++++ serde_arrow/benches/groups/mod.rs | 1 + serde_arrow/benches/serde_arrow_bench.rs | 1 + 6 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 serde_arrow/benches/groups/json_to_arrow.rs diff --git a/Cargo.lock b/Cargo.lock index fdf2dab7..dc43dda2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1904,6 +1904,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-transcode" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" +dependencies = [ + "serde", +] + [[package]] name = "serde_arrow" version = "0.10.0-rc.1" @@ -1977,6 +1986,7 @@ dependencies = [ "rand", "rust_decimal", "serde", + "serde-transcode", "serde_json", ] diff --git a/serde_arrow/Cargo.toml b/serde_arrow/Cargo.toml index d8a304b6..64e9df06 100644 --- a/serde_arrow/Cargo.toml +++ b/serde_arrow/Cargo.toml @@ -128,6 +128,7 @@ bigdecimal = {version = "0.4", features = ["serde"] } arrow-json-50 = { package = "arrow-json", version = "50" } criterion = "0.4" arrow2_convert = "0.5.0" +serde-transcode = "1" [dev-dependencies.rust_decimal] version = "1.33" diff --git a/serde_arrow/benches/groups/complex_common.rs b/serde_arrow/benches/groups/complex_common.rs index 6e7dc570..d9716780 100644 --- a/serde_arrow/benches/groups/complex_common.rs +++ b/serde_arrow/benches/groups/complex_common.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_arrow::_impl::arrow2; #[derive(Debug, Serialize, Deserialize, ArrowField, ArrowSerialize, ArrowDeserialize)] -struct Item { +pub(crate) struct Item { string: String, points: Vec, child: SubItem, @@ -30,7 +30,7 @@ struct SubItem { } impl Item { - fn random(rng: &mut R) -> Self { + pub(crate) fn random(rng: &mut R) -> Self { let n_string = Uniform::new(1, 50).sample(rng); let n_points = Uniform::new(1, 50).sample(rng); diff --git a/serde_arrow/benches/groups/json_to_arrow.rs b/serde_arrow/benches/groups/json_to_arrow.rs new file mode 100644 index 00000000..4214fffa --- /dev/null +++ b/serde_arrow/benches/groups/json_to_arrow.rs @@ -0,0 +1,101 @@ +use crate::groups::complex_common::Item; + +use { + serde_arrow::schema::{SchemaLike as _, SerdeArrowSchema}, + std::sync::Arc, +}; + +// arrow-version:replace: use arrow_json_{version}::ReaderBuilder; +use arrow_json_50::ReaderBuilder; + +// arrow-version:replace: use arrow_schema_{version}::Schema as ArrowSchema; +use arrow_schema_50::Schema as ArrowSchema; + +// arrow-version:replace: use arrow_array_{version}::RecordBatch; +use arrow_array_50::RecordBatch; + +fn benchmark_json_to_arrow(c: &mut criterion::Criterion) { + let rng = &mut rand::thread_rng(); + let items = (0..10_000) + .map(|_| Item::random(rng)) + .collect::>(); + let jsons_to_deserialize = items + .iter() + .map(|item| serde_json::to_string(item).expect("Failed to serialize JSON")) + .collect::>(); + let jsons_to_deserialize_concatenated = jsons_to_deserialize.join("\n"); + let jsons_to_deserialize: Vec<&str> = { + let mut prev = 0; + jsons_to_deserialize + .iter() + .map(|s| { + let ret = &jsons_to_deserialize_concatenated[prev..(prev + s.len())]; + prev += s.len() + 1; + ret + }) + .collect::>() + }; + let schema = SerdeArrowSchema::from_samples(&items, Default::default()).unwrap(); + let arrow_fields = schema.to_arrow_fields().unwrap(); + let mut group = c.benchmark_group(format!("json_to_arrow({})", items.len())); + + // arrow-json direct + group.bench_function("arrow-json", |b| { + b.iter(|| { + let schema = Arc::new(ArrowSchema::new(arrow_fields.to_owned())); + let mut decoder = ReaderBuilder::new(schema.clone()).build_decoder().unwrap(); + decoder + .decode(criterion::black_box( + jsons_to_deserialize_concatenated.as_bytes(), + )) + .unwrap(); + let arrays = decoder.flush().unwrap().unwrap().columns().to_vec(); + let record_batch = RecordBatch::try_new(schema, arrays).unwrap(); + record_batch + }) + }); + + // arrow-json via serde + group.bench_function("serde_json_transcode_arrow-json", |b| { + b.iter(|| { + let schema = Arc::new(ArrowSchema::new(arrow_fields.to_owned())); + let mut decoder = ReaderBuilder::new(schema.clone()).build_decoder().unwrap(); + let mut deserializers = jsons_to_deserialize + .iter() + .map(|json_to_deserialize| { + serde_json::Deserializer::from_slice(criterion::black_box( + json_to_deserialize.as_bytes(), + )) + }) + .collect::>(); + let transcoders = deserializers + .iter_mut() + .map(|deserializer| serde_transcode::Transcoder::new(deserializer)) + .collect::>(); + decoder.serialize(&transcoders).unwrap(); + let arrays = decoder.flush().unwrap().unwrap().columns().to_vec(); + let record_batch = RecordBatch::try_new(schema, arrays).unwrap(); + record_batch + }) + }); + + // serde_arrow via serde + group.bench_function("serde_json_transcode_serde_arrow", |b| { + b.iter(|| { + let mut arrow_builder = serde_arrow::ArrowBuilder::new(&arrow_fields).unwrap(); + for json_to_deserialize in &jsons_to_deserialize { + let mut deserializer = serde_json::Deserializer::from_slice(criterion::black_box( + json_to_deserialize.as_bytes(), + )); + let transcoder = serde_transcode::Transcoder::new(&mut deserializer); + arrow_builder.push(&transcoder).unwrap(); + } + let arrays = arrow_builder.build_arrays().unwrap(); + let schema = ArrowSchema::new(arrow_fields.to_owned()); + let record_batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap(); + record_batch + }) + }); +} + +criterion::criterion_group!(benchmark, benchmark_json_to_arrow); diff --git a/serde_arrow/benches/groups/mod.rs b/serde_arrow/benches/groups/mod.rs index 2790940c..13b785fa 100644 --- a/serde_arrow/benches/groups/mod.rs +++ b/serde_arrow/benches/groups/mod.rs @@ -1,3 +1,4 @@ pub mod complex_common; pub mod impls; +pub mod json_to_arrow; pub mod primitives; diff --git a/serde_arrow/benches/serde_arrow_bench.rs b/serde_arrow/benches/serde_arrow_bench.rs index 54391d47..f707e06a 100644 --- a/serde_arrow/benches/serde_arrow_bench.rs +++ b/serde_arrow/benches/serde_arrow_bench.rs @@ -3,4 +3,5 @@ mod groups; criterion::criterion_main!( groups::complex_common::benchmark, groups::primitives::benchmark, + groups::json_to_arrow::benchmark, ); From 11dc93c95e8830336d415bf4c5d15243711a6c2a Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 2 Feb 2024 22:38:13 +0100 Subject: [PATCH 3/8] Allow serializing unit as null to enable transcoding --- serde_arrow/src/internal/serialization_ng/bool_builder.rs | 2 +- .../src/internal/serialization_ng/date64_builder.rs | 2 +- .../src/internal/serialization_ng/decimal_builder.rs | 2 +- .../internal/serialization_ng/dictionary_utf8_builder.rs | 6 +++--- serde_arrow/src/internal/serialization_ng/float_builder.rs | 6 +++--- serde_arrow/src/internal/serialization_ng/int_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/map_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/null_builder.rs | 5 ----- .../src/internal/serialization_ng/struct_builder.rs | 4 ++-- serde_arrow/src/internal/serialization_ng/utf8_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/utils.rs | 7 +++++-- 11 files changed, 19 insertions(+), 21 deletions(-) diff --git a/serde_arrow/src/internal/serialization_ng/bool_builder.rs b/serde_arrow/src/internal/serialization_ng/bool_builder.rs index efaa79c2..903148ab 100644 --- a/serde_arrow/src/internal/serialization_ng/bool_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/bool_builder.rs @@ -39,7 +39,7 @@ impl SimpleSerializer for BoolBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(false); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/date64_builder.rs b/serde_arrow/src/internal/serialization_ng/date64_builder.rs index 4119ad76..e5c4e495 100644 --- a/serde_arrow/src/internal/serialization_ng/date64_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/date64_builder.rs @@ -48,7 +48,7 @@ impl SimpleSerializer for Date64Builder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/decimal_builder.rs b/serde_arrow/src/internal/serialization_ng/decimal_builder.rs index 2ce6fa75..ef04af74 100644 --- a/serde_arrow/src/internal/serialization_ng/decimal_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/decimal_builder.rs @@ -60,7 +60,7 @@ impl SimpleSerializer for DecimalBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs b/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs index b98ea456..d097e0fc 100644 --- a/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs @@ -47,11 +47,11 @@ impl SimpleSerializer for DictionaryUtf8Builder { } fn serialize_default(&mut self) -> Result<()> { - self.indices.serialize_none() + self.indices.serialize_unit() } - fn serialize_none(&mut self) -> Result<()> { - self.indices.serialize_none() + fn serialize_unit(&mut self) -> Result<()> { + self.indices.serialize_unit() } fn serialize_str(&mut self, v: &str) -> Result<()> { diff --git a/serde_arrow/src/internal/serialization_ng/float_builder.rs b/serde_arrow/src/internal/serialization_ng/float_builder.rs index dc3c62f6..7322b4f3 100644 --- a/serde_arrow/src/internal/serialization_ng/float_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/float_builder.rs @@ -46,7 +46,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0.0); Ok(()) @@ -108,7 +108,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0.0); Ok(()) @@ -166,7 +166,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(f16::ZERO); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/int_builder.rs b/serde_arrow/src/internal/serialization_ng/int_builder.rs index 6d6131aa..2ca8b1a4 100644 --- a/serde_arrow/src/internal/serialization_ng/int_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/int_builder.rs @@ -58,7 +58,7 @@ where Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(I::default()); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/map_builder.rs b/serde_arrow/src/internal/serialization_ng/map_builder.rs index fa83881c..a71dd280 100644 --- a/serde_arrow/src/internal/serialization_ng/map_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/map_builder.rs @@ -56,7 +56,7 @@ impl SimpleSerializer for MapBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { self.offsets.push_current_items(); push_validity(&mut self.validity, false) } diff --git a/serde_arrow/src/internal/serialization_ng/null_builder.rs b/serde_arrow/src/internal/serialization_ng/null_builder.rs index 2f6c7bf0..c0b44d7d 100644 --- a/serde_arrow/src/internal/serialization_ng/null_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/null_builder.rs @@ -33,11 +33,6 @@ impl SimpleSerializer for NullBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { - self.count += 1; - Ok(()) - } - fn serialize_unit(&mut self) -> Result<()> { self.count += 1; Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/struct_builder.rs b/serde_arrow/src/internal/serialization_ng/struct_builder.rs index 22e14641..26da25b4 100644 --- a/serde_arrow/src/internal/serialization_ng/struct_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/struct_builder.rs @@ -110,7 +110,7 @@ impl StructBuilder { ); } - self.named_fields[idx].1.serialize_none()?; + self.named_fields[idx].1.serialize_unit()?; } } Ok(()) @@ -142,7 +142,7 @@ impl SimpleSerializer for StructBuilder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; for (_, field) in &mut self.named_fields { diff --git a/serde_arrow/src/internal/serialization_ng/utf8_builder.rs b/serde_arrow/src/internal/serialization_ng/utf8_builder.rs index 51f372d5..fb9c3968 100644 --- a/serde_arrow/src/internal/serialization_ng/utf8_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/utf8_builder.rs @@ -45,7 +45,7 @@ impl SimpleSerializer for Utf8Builder { Ok(()) } - fn serialize_none(&mut self) -> Result<()> { + fn serialize_unit(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.offsets.push_current_items(); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/utils.rs b/serde_arrow/src/internal/serialization_ng/utils.rs index 7083a070..c0411c32 100644 --- a/serde_arrow/src/internal/serialization_ng/utils.rs +++ b/serde_arrow/src/internal/serialization_ng/utils.rs @@ -53,11 +53,14 @@ pub trait SimpleSerializer: Sized { } fn serialize_unit(&mut self) -> Result<()> { - fail!("serialize_unit is not supported for {}", self.name()); + fail!( + "serialize_unit/serialize_none is not supported for {}", + self.name() + ); } fn serialize_none(&mut self) -> Result<()> { - fail!("serialize_none is not supported for {}", self.name()); + self.serialize_unit() } fn serialize_some(&mut self, value: &V) -> Result<()> { From c0fa5ca3ea1d35c7c7147c347bb9f90b1db3e6ab Mon Sep 17 00:00:00 2001 From: Christopher Prohm Date: Sun, 18 Feb 2024 09:38:34 +0100 Subject: [PATCH 4/8] Switch serialize_unit <-> serialize_none roles --- serde_arrow/src/internal/serialization_ng/bool_builder.rs | 2 +- .../src/internal/serialization_ng/date64_builder.rs | 2 +- .../src/internal/serialization_ng/decimal_builder.rs | 2 +- .../internal/serialization_ng/dictionary_utf8_builder.rs | 6 +++--- .../src/internal/serialization_ng/float_builder.rs | 6 +++--- serde_arrow/src/internal/serialization_ng/int_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/map_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/null_builder.rs | 2 +- .../src/internal/serialization_ng/struct_builder.rs | 4 ++-- serde_arrow/src/internal/serialization_ng/utf8_builder.rs | 2 +- serde_arrow/src/internal/serialization_ng/utils.rs | 8 ++++---- serde_arrow/src/test_impls/json_values.rs | 4 ++-- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/serde_arrow/src/internal/serialization_ng/bool_builder.rs b/serde_arrow/src/internal/serialization_ng/bool_builder.rs index 903148ab..efaa79c2 100644 --- a/serde_arrow/src/internal/serialization_ng/bool_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/bool_builder.rs @@ -39,7 +39,7 @@ impl SimpleSerializer for BoolBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(false); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/date64_builder.rs b/serde_arrow/src/internal/serialization_ng/date64_builder.rs index e5c4e495..4119ad76 100644 --- a/serde_arrow/src/internal/serialization_ng/date64_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/date64_builder.rs @@ -48,7 +48,7 @@ impl SimpleSerializer for Date64Builder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/decimal_builder.rs b/serde_arrow/src/internal/serialization_ng/decimal_builder.rs index ef04af74..2ce6fa75 100644 --- a/serde_arrow/src/internal/serialization_ng/decimal_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/decimal_builder.rs @@ -60,7 +60,7 @@ impl SimpleSerializer for DecimalBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs b/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs index d097e0fc..b98ea456 100644 --- a/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/dictionary_utf8_builder.rs @@ -47,11 +47,11 @@ impl SimpleSerializer for DictionaryUtf8Builder { } fn serialize_default(&mut self) -> Result<()> { - self.indices.serialize_unit() + self.indices.serialize_none() } - fn serialize_unit(&mut self) -> Result<()> { - self.indices.serialize_unit() + fn serialize_none(&mut self) -> Result<()> { + self.indices.serialize_none() } fn serialize_str(&mut self, v: &str) -> Result<()> { diff --git a/serde_arrow/src/internal/serialization_ng/float_builder.rs b/serde_arrow/src/internal/serialization_ng/float_builder.rs index 7322b4f3..dc3c62f6 100644 --- a/serde_arrow/src/internal/serialization_ng/float_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/float_builder.rs @@ -46,7 +46,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0.0); Ok(()) @@ -108,7 +108,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(0.0); Ok(()) @@ -166,7 +166,7 @@ impl SimpleSerializer for FloatBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(f16::ZERO); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/int_builder.rs b/serde_arrow/src/internal/serialization_ng/int_builder.rs index 2ca8b1a4..6d6131aa 100644 --- a/serde_arrow/src/internal/serialization_ng/int_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/int_builder.rs @@ -58,7 +58,7 @@ where Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.buffer.push(I::default()); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/map_builder.rs b/serde_arrow/src/internal/serialization_ng/map_builder.rs index a71dd280..fa83881c 100644 --- a/serde_arrow/src/internal/serialization_ng/map_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/map_builder.rs @@ -56,7 +56,7 @@ impl SimpleSerializer for MapBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { self.offsets.push_current_items(); push_validity(&mut self.validity, false) } diff --git a/serde_arrow/src/internal/serialization_ng/null_builder.rs b/serde_arrow/src/internal/serialization_ng/null_builder.rs index c0b44d7d..5a7e3122 100644 --- a/serde_arrow/src/internal/serialization_ng/null_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/null_builder.rs @@ -33,7 +33,7 @@ impl SimpleSerializer for NullBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { self.count += 1; Ok(()) } diff --git a/serde_arrow/src/internal/serialization_ng/struct_builder.rs b/serde_arrow/src/internal/serialization_ng/struct_builder.rs index 26da25b4..22e14641 100644 --- a/serde_arrow/src/internal/serialization_ng/struct_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/struct_builder.rs @@ -110,7 +110,7 @@ impl StructBuilder { ); } - self.named_fields[idx].1.serialize_unit()?; + self.named_fields[idx].1.serialize_none()?; } } Ok(()) @@ -142,7 +142,7 @@ impl SimpleSerializer for StructBuilder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; for (_, field) in &mut self.named_fields { diff --git a/serde_arrow/src/internal/serialization_ng/utf8_builder.rs b/serde_arrow/src/internal/serialization_ng/utf8_builder.rs index fb9c3968..51f372d5 100644 --- a/serde_arrow/src/internal/serialization_ng/utf8_builder.rs +++ b/serde_arrow/src/internal/serialization_ng/utf8_builder.rs @@ -45,7 +45,7 @@ impl SimpleSerializer for Utf8Builder { Ok(()) } - fn serialize_unit(&mut self) -> Result<()> { + fn serialize_none(&mut self) -> Result<()> { push_validity(&mut self.validity, false)?; self.offsets.push_current_items(); Ok(()) diff --git a/serde_arrow/src/internal/serialization_ng/utils.rs b/serde_arrow/src/internal/serialization_ng/utils.rs index c0411c32..bf2e8390 100644 --- a/serde_arrow/src/internal/serialization_ng/utils.rs +++ b/serde_arrow/src/internal/serialization_ng/utils.rs @@ -53,16 +53,16 @@ pub trait SimpleSerializer: Sized { } fn serialize_unit(&mut self) -> Result<()> { + self.serialize_none() + } + + fn serialize_none(&mut self) -> Result<()> { fail!( "serialize_unit/serialize_none is not supported for {}", self.name() ); } - fn serialize_none(&mut self) -> Result<()> { - self.serialize_unit() - } - fn serialize_some(&mut self, value: &V) -> Result<()> { value.serialize(Mut(self)) } diff --git a/serde_arrow/src/test_impls/json_values.rs b/serde_arrow/src/test_impls/json_values.rs index 4264c250..50b1fe85 100644 --- a/serde_arrow/src/test_impls/json_values.rs +++ b/serde_arrow/src/test_impls/json_values.rs @@ -79,7 +79,7 @@ fn serde_json_nullable_strings_non_nullable_field() { ])); test.try_serialize_arrow(&items) - .assert_error("serialize_unit is not supported for Utf8Builder"); + .assert_error("cannot push null for non-nullable array"); test.try_serialize_arrow2(&items) - .assert_error("serialize_unit is not supported for Utf8Builder"); + .assert_error("cannot push null for non-nullable array"); } From 8d04dfe7b3f798822fa2aa36381ef7bf6154a30f Mon Sep 17 00:00:00 2001 From: Christopher Prohm Date: Sun, 18 Feb 2024 09:42:49 +0100 Subject: [PATCH 5/8] Add test for nullable values in json --- serde_arrow/src/test_impls/json_values.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/serde_arrow/src/test_impls/json_values.rs b/serde_arrow/src/test_impls/json_values.rs index 50b1fe85..c53485de 100644 --- a/serde_arrow/src/test_impls/json_values.rs +++ b/serde_arrow/src/test_impls/json_values.rs @@ -22,7 +22,10 @@ fn serde_json_mixed_ints() { #[test] fn serde_json_mixed_fixed_schema() { - let items = vec![json!({ "a": 1, "b": -2 }), json!({ "a": 3.0, "b": 4 })]; + let items = json!([ + { "a": 1, "b": -2 }, + { "a": 3.0, "b": 4 }, + ]); Test::new() .with_schema(json!([ {"name": "a", "data_type": "F64"}, @@ -31,6 +34,23 @@ fn serde_json_mixed_fixed_schema() { .serialize(&items); } +#[test] +fn serde_json_mixed_fixed_schema_nullable() { + let items = json!([ + { "a": 1, "b": -2, "c": "hello", "d": null }, + { "a": null, "b": 4, "c": null, "d": true }, + { "a": 3.0, "b": null, "c": "world", "d": false }, + ]); + Test::new() + .with_schema(json!([ + {"name": "a", "data_type": "F64", "nullable": true}, + {"name": "b", "data_type": "I64", "nullable": true}, + {"name": "c", "data_type": "Utf8", "nullable": true}, + {"name": "d", "data_type": "Bool", "nullable": true}, + ])) + .serialize(&items); +} + #[test] fn serde_json_mixed_fixed_schema_outer_array() { let items = json!([{ "a": 1, "b": -2 }, { "a": 3.0, "b": 4 }]); From 9db4b9c655f5f4ce6dbba5669adced8de755a995 Mon Sep 17 00:00:00 2001 From: Christopher Prohm Date: Sun, 18 Feb 2024 10:58:57 +0100 Subject: [PATCH 6/8] Tune benchmarks --- Cargo.lock | 99 +++++++++++++++++--- serde_arrow/Cargo.toml | 1 + serde_arrow/benches/groups/complex_common.rs | 4 +- serde_arrow/benches/groups/impls.rs | 18 +++- serde_arrow/benches/groups/json_to_arrow.rs | 80 ++++++++++++---- 5 files changed, 165 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc43dda2..651fda3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,7 +898,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", "syn_derive", ] @@ -947,7 +947,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", ] [[package]] @@ -1172,7 +1172,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.11", + "syn 2.0.49", ] [[package]] @@ -1189,7 +1189,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", ] [[package]] @@ -1240,6 +1240,15 @@ dependencies = [ "serde_arrow", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "foreign_vec" version = "0.1.0" @@ -1282,6 +1291,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "halfbrown" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5681137554ddff44396e5f149892c769d45301dd9aa19c51602a89ee214cb0ec" +dependencies = [ + "hashbrown 0.13.2", + "serde", +] + [[package]] name = "hash_hasher" version = "2.0.3" @@ -1302,6 +1321,9 @@ name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] [[package]] name = "hashbrown" @@ -1672,9 +1694,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.54" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -1701,9 +1723,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1764,6 +1786,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "ref-cast" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] + [[package]] name = "regex" version = "1.10.2" @@ -1988,6 +2030,7 @@ dependencies = [ "serde", "serde-transcode", "serde_json", + "simd-json", ] [[package]] @@ -1998,7 +2041,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", ] [[package]] @@ -2012,6 +2055,22 @@ dependencies = [ "serde", ] +[[package]] +name = "simd-json" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" +dependencies = [ + "getrandom", + "halfbrown", + "lexical-core", + "ref-cast", + "serde", + "serde_json", + "simdutf8", + "value-trait", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -2037,9 +2096,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.11" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -2055,7 +2114,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", ] [[package]] @@ -2166,6 +2225,18 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +[[package]] +name = "value-trait" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" +dependencies = [ + "float-cmp", + "halfbrown", + "itoa", + "ryu", +] + [[package]] name = "version_check" version = "0.9.4" @@ -2209,7 +2280,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", "wasm-bindgen-shared", ] @@ -2231,7 +2302,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/serde_arrow/Cargo.toml b/serde_arrow/Cargo.toml index 64e9df06..f78b62a4 100644 --- a/serde_arrow/Cargo.toml +++ b/serde_arrow/Cargo.toml @@ -129,6 +129,7 @@ arrow-json-50 = { package = "arrow-json", version = "50" } criterion = "0.4" arrow2_convert = "0.5.0" serde-transcode = "1" +simd-json = "0.13.8" [dev-dependencies.rust_decimal] version = "1.33" diff --git a/serde_arrow/benches/groups/complex_common.rs b/serde_arrow/benches/groups/complex_common.rs index d9716780..247bf3be 100644 --- a/serde_arrow/benches/groups/complex_common.rs +++ b/serde_arrow/benches/groups/complex_common.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_arrow::_impl::arrow2; #[derive(Debug, Serialize, Deserialize, ArrowField, ArrowSerialize, ArrowDeserialize)] -pub(crate) struct Item { +pub struct Item { string: String, points: Vec, child: SubItem, @@ -30,7 +30,7 @@ struct SubItem { } impl Item { - pub(crate) fn random(rng: &mut R) -> Self { + pub fn random(rng: &mut R) -> Self { let n_string = Uniform::new(1, 50).sample(rng); let n_points = Uniform::new(1, 50).sample(rng); diff --git a/serde_arrow/benches/groups/impls.rs b/serde_arrow/benches/groups/impls.rs index b61980b1..40111977 100644 --- a/serde_arrow/benches/groups/impls.rs +++ b/serde_arrow/benches/groups/impls.rs @@ -16,12 +16,20 @@ macro_rules! define_benchmark { for n in [$($n),*] { let mut group = c.benchmark_group(format!("{}_serialize({})", stringify!($name), n)); - group.sample_size(20); + group.sampling_mode(criterion::SamplingMode::Flat); - group.measurement_time(std::time::Duration::from_secs(120)); + if !crate::groups::impls::is_quick() { + group.sample_size(20); + group.measurement_time(std::time::Duration::from_secs(120)); + } else { + group.sample_size(10); + group.measurement_time(std::time::Duration::from_secs(5)); + } + + let n_items = if !crate::groups::impls::is_quick() { n } else { n / 1000 }; let mut rng = rand::thread_rng(); - let items = (0..n) + let items = (0..n_items) .map(|_| <$ty>::random(&mut rng)) .collect::>(); let schema = SerdeArrowSchema::from_samples(&items, Default::default()).unwrap(); @@ -166,3 +174,7 @@ pub fn random_string(rng: &mut R, length: Range) -> Stri .map(|_| -> char { Standard.sample(rng) }) .collect() } + +pub fn is_quick() -> bool { + std::env::var("SERDE_ARROW_BENCH_QUICK").is_ok() +} diff --git a/serde_arrow/benches/groups/json_to_arrow.rs b/serde_arrow/benches/groups/json_to_arrow.rs index 4214fffa..769c8ec0 100644 --- a/serde_arrow/benches/groups/json_to_arrow.rs +++ b/serde_arrow/benches/groups/json_to_arrow.rs @@ -13,6 +13,7 @@ use arrow_schema_50::Schema as ArrowSchema; // arrow-version:replace: use arrow_array_{version}::RecordBatch; use arrow_array_50::RecordBatch; +use serde_json::Value; fn benchmark_json_to_arrow(c: &mut criterion::Criterion) { let rng = &mut rand::thread_rng(); @@ -35,37 +36,34 @@ fn benchmark_json_to_arrow(c: &mut criterion::Criterion) { }) .collect::>() }; - let schema = SerdeArrowSchema::from_samples(&items, Default::default()).unwrap(); + + let schema = SerdeArrowSchema::from_type::(Default::default()).unwrap(); let arrow_fields = schema.to_arrow_fields().unwrap(); - let mut group = c.benchmark_group(format!("json_to_arrow({})", items.len())); + let mut group = c.benchmark_group("json_to_arrow"); // arrow-json direct - group.bench_function("arrow-json", |b| { + group.bench_function("arrow_json", |b| { b.iter(|| { let schema = Arc::new(ArrowSchema::new(arrow_fields.to_owned())); let mut decoder = ReaderBuilder::new(schema.clone()).build_decoder().unwrap(); decoder - .decode(criterion::black_box( - jsons_to_deserialize_concatenated.as_bytes(), - )) + .decode(jsons_to_deserialize_concatenated.as_bytes()) .unwrap(); let arrays = decoder.flush().unwrap().unwrap().columns().to_vec(); let record_batch = RecordBatch::try_new(schema, arrays).unwrap(); - record_batch + criterion::black_box(record_batch) }) }); // arrow-json via serde - group.bench_function("serde_json_transcode_arrow-json", |b| { + group.bench_function("arrow_json (serde_json,transcode)", |b| { b.iter(|| { let schema = Arc::new(ArrowSchema::new(arrow_fields.to_owned())); let mut decoder = ReaderBuilder::new(schema.clone()).build_decoder().unwrap(); let mut deserializers = jsons_to_deserialize .iter() .map(|json_to_deserialize| { - serde_json::Deserializer::from_slice(criterion::black_box( - json_to_deserialize.as_bytes(), - )) + serde_json::Deserializer::from_slice(json_to_deserialize.as_bytes()) }) .collect::>(); let transcoders = deserializers @@ -75,25 +73,71 @@ fn benchmark_json_to_arrow(c: &mut criterion::Criterion) { decoder.serialize(&transcoders).unwrap(); let arrays = decoder.flush().unwrap().unwrap().columns().to_vec(); let record_batch = RecordBatch::try_new(schema, arrays).unwrap(); - record_batch + criterion::black_box(record_batch) }) }); // serde_arrow via serde - group.bench_function("serde_json_transcode_serde_arrow", |b| { + group.bench_function("serde_arrow (serde_json,transcode)", |b| { b.iter(|| { let mut arrow_builder = serde_arrow::ArrowBuilder::new(&arrow_fields).unwrap(); - for json_to_deserialize in &jsons_to_deserialize { - let mut deserializer = serde_json::Deserializer::from_slice(criterion::black_box( - json_to_deserialize.as_bytes(), - )); + for json_to_deserialize in jsons_to_deserialize_concatenated + .as_bytes() + .split(|c| *c == b'\n') + .filter(|s| !s.is_empty()) + { + let mut deserializer = serde_json::Deserializer::from_slice(json_to_deserialize); let transcoder = serde_transcode::Transcoder::new(&mut deserializer); arrow_builder.push(&transcoder).unwrap(); } + + let arrays = arrow_builder.build_arrays().unwrap(); + let schema = ArrowSchema::new(arrow_fields.to_owned()); + let record_batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap(); + criterion::black_box(record_batch) + }) + }); + + group.bench_function("serde_arrow (serde_json,value)", |b| { + b.iter(|| { + let mut arrow_builder = serde_arrow::ArrowBuilder::new(&arrow_fields).unwrap(); + for json_to_deserialize in jsons_to_deserialize_concatenated + .as_bytes() + .split(|c| *c == b'\n') + .filter(|s| !s.is_empty()) + { + let item: Value = serde_json::from_slice(json_to_deserialize).unwrap(); + arrow_builder.push(&item).unwrap(); + } + + let arrays = arrow_builder.build_arrays().unwrap(); + let schema = ArrowSchema::new(arrow_fields.to_owned()); + + let record_batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap(); + criterion::black_box(record_batch) + }); + }); + + group.bench_function("serde_arrow (simd_json,transcode)", |b| { + b.iter(|| { + let mut arrow_builder = serde_arrow::ArrowBuilder::new(&arrow_fields).unwrap(); + let mut jsons_to_deserialize_concatenated = + jsons_to_deserialize_concatenated.as_bytes().to_vec(); + + for json_to_deserialize in jsons_to_deserialize_concatenated + .split_mut(|c| *c == b'\n') + .filter(|s| !s.is_empty()) + { + let mut deserializer = + simd_json::Deserializer::from_slice(json_to_deserialize).unwrap(); + let transcoder = serde_transcode::Transcoder::new(&mut deserializer); + arrow_builder.push(&transcoder).unwrap(); + } + let arrays = arrow_builder.build_arrays().unwrap(); let schema = ArrowSchema::new(arrow_fields.to_owned()); let record_batch = RecordBatch::try_new(Arc::new(schema), arrays).unwrap(); - record_batch + criterion::black_box(record_batch) }) }); } From 1ca4ceea497e314509bf31fedd7e89e70f5a0629 Mon Sep 17 00:00:00 2001 From: Christopher Prohm Date: Sun, 18 Feb 2024 10:59:14 +0100 Subject: [PATCH 7/8] Do not include JSON benchmarks in Readme --- x.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/x.py b/x.py index 385c727e..9fcf024b 100644 --- a/x.py +++ b/x.py @@ -98,7 +98,7 @@ def precommit(backtrace=False): update_workflows() format() - lint() + check() test(backtrace=backtrace) example() @@ -162,9 +162,9 @@ def format(): _sh("cargo fmt") -@cmd(help="Run the linting") +@cmd(help="Run the linters") @arg("--fast", action="store_true") -def lint(fast=False): +def check(fast=False): check_cargo_toml() _sh(f"cargo check --features {default_features}") _sh(f"cargo clippy --features {default_features}") @@ -293,8 +293,8 @@ def summarize_bench(update=False): print(format_benchmark(mean_times)) if update: - update_readme(mean_times) - plot_times(mean_times) + update_readme(mean_times, ignore_groups={"json_to_arrow"}) + plot_times(mean_times, ignore_groups={"json_to_arrow"}) def load_times(): @@ -333,7 +333,7 @@ def load_times(): return mean_times -def update_readme(mean_times): +def update_readme(mean_times, ignore_groups=()): print("Update readme") with open(self_path / "Readme.md", "rt", encoding="utf8") as fobj: lines = [line.rstrip() for line in fobj] @@ -348,12 +348,15 @@ def update_readme(mean_times): else: if line.strip() == "": - print(format_benchmark(mean_times), file=fobj) + print( + format_benchmark(mean_times, ignore_groups=ignore_groups), + file=fobj, + ) print(line, file=fobj) active = False -def plot_times(mean_times): +def plot_times(mean_times, ignore_groups=()): print("Plot times") import matplotlib.pyplot as plt @@ -363,6 +366,7 @@ def plot_times(mean_times): [ {"group": group, "impl": impl, "time": time} for (group, impl), time in mean_times.items() + if group not in ignore_groups ] ) agg_df = ( @@ -405,9 +409,9 @@ def plot_times(mean_times): plt.savefig(self_path / "timings.png") -def format_benchmark(mean_times): +def format_benchmark(mean_times, ignore_groups=()): def _parts(): - for group in sorted({g for g, _ in mean_times}): + for group in sorted({g for g, _ in mean_times if g not in ignore_groups}): times_in_group = {n: v for (g, n), v in mean_times.items() if g == group} sorted_items = sorted(times_in_group.items(), key=lambda kv: kv[1]) From b3b3cf87e6ce49079f313e5bba3ca59945925bab Mon Sep 17 00:00:00 2001 From: Christopher Prohm Date: Sun, 18 Feb 2024 11:45:04 +0100 Subject: [PATCH 8/8] Re-run benchmarks --- Readme.md | 32 ++++++++++++++++---------------- timings.png | Bin 37734 -> 37816 bytes 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Readme.md b/Readme.md index 48169099..944dd428 100644 --- a/Readme.md +++ b/Readme.md @@ -109,37 +109,37 @@ The detailed runtimes of the [benchmarks](./serde_arrow/benches/groups/) are lis | label | time [ms] | arrow2_convert: | serde_arrow::to | serde_arrow::to | arrow_json::Rea | |------------------------------|-----------|-----------------|-----------------|-----------------|-----------------| -| arrow2_convert::TryIntoArrow | 50.41 | 1.00 | 0.33 | 0.31 | 0.13 | -| serde_arrow::to_arrow2 | 150.91 | 2.99 | 1.00 | 0.94 | 0.40 | -| serde_arrow::to_arrow | 161.25 | 3.20 | 1.07 | 1.00 | 0.43 | -| arrow_json::ReaderBuilder | 375.00 | 7.44 | 2.48 | 2.33 | 1.00 | +| arrow2_convert::TryIntoArrow | 50.25 | 1.00 | 0.33 | 0.30 | 0.13 | +| serde_arrow::to_arrow2 | 152.02 | 3.03 | 1.00 | 0.92 | 0.41 | +| serde_arrow::to_arrow | 164.94 | 3.28 | 1.09 | 1.00 | 0.44 | +| arrow_json::ReaderBuilder | 375.15 | 7.47 | 2.47 | 2.27 | 1.00 | ### complex_common_serialize(1000000) | label | time [ms] | arrow2_convert: | serde_arrow::to | serde_arrow::to | arrow_json::Rea | |------------------------------|-----------|-----------------|-----------------|-----------------|-----------------| -| arrow2_convert::TryIntoArrow | 470.21 | 1.00 | 0.32 | 0.30 | 0.13 | -| serde_arrow::to_arrow2 | 1448.66 | 3.08 | 1.00 | 0.92 | 0.40 | -| serde_arrow::to_arrow | 1574.82 | 3.35 | 1.09 | 1.00 | 0.43 | -| arrow_json::ReaderBuilder | 3628.26 | 7.72 | 2.50 | 2.30 | 1.00 | +| arrow2_convert::TryIntoArrow | 475.17 | 1.00 | 0.32 | 0.30 | 0.13 | +| serde_arrow::to_arrow2 | 1464.57 | 3.08 | 1.00 | 0.92 | 0.40 | +| serde_arrow::to_arrow | 1584.39 | 3.33 | 1.08 | 1.00 | 0.43 | +| arrow_json::ReaderBuilder | 3656.23 | 7.69 | 2.50 | 2.31 | 1.00 | ### primitives_serialize(100000) | label | time [ms] | arrow2_convert: | serde_arrow::to | serde_arrow::to | arrow_json::Rea | |------------------------------|-----------|-----------------|-----------------|-----------------|-----------------| -| arrow2_convert::TryIntoArrow | 14.11 | 1.00 | 0.46 | 0.32 | 0.11 | -| serde_arrow::to_arrow2 | 30.85 | 2.19 | 1.00 | 0.70 | 0.25 | -| serde_arrow::to_arrow | 44.02 | 3.12 | 1.43 | 1.00 | 0.35 | -| arrow_json::ReaderBuilder | 125.16 | 8.87 | 4.06 | 2.84 | 1.00 | +| arrow2_convert::TryIntoArrow | 13.12 | 1.00 | 0.40 | 0.29 | 0.10 | +| serde_arrow::to_arrow2 | 32.46 | 2.47 | 1.00 | 0.71 | 0.25 | +| serde_arrow::to_arrow | 45.47 | 3.47 | 1.40 | 1.00 | 0.35 | +| arrow_json::ReaderBuilder | 130.77 | 9.97 | 4.03 | 2.88 | 1.00 | ### primitives_serialize(1000000) | label | time [ms] | arrow2_convert: | serde_arrow::to | serde_arrow::to | arrow_json::Rea | |------------------------------|-----------|-----------------|-----------------|-----------------|-----------------| -| arrow2_convert::TryIntoArrow | 144.14 | 1.00 | 0.43 | 0.33 | 0.11 | -| serde_arrow::to_arrow2 | 333.09 | 2.31 | 1.00 | 0.75 | 0.24 | -| serde_arrow::to_arrow | 441.65 | 3.06 | 1.33 | 1.00 | 0.32 | -| arrow_json::ReaderBuilder | 1362.27 | 9.45 | 4.09 | 3.08 | 1.00 | +| arrow2_convert::TryIntoArrow | 151.80 | 1.00 | 0.44 | 0.32 | 0.11 | +| serde_arrow::to_arrow2 | 344.82 | 2.27 | 1.00 | 0.73 | 0.25 | +| serde_arrow::to_arrow | 473.56 | 3.12 | 1.37 | 1.00 | 0.34 | +| arrow_json::ReaderBuilder | 1403.08 | 9.24 | 4.07 | 2.96 | 1.00 | diff --git a/timings.png b/timings.png index 8930d803b7d754bc7dffe78dc9505daa2712eec2..e6022942cbd6d98dfc5b4aac4ca75de88844cf91 100644 GIT binary patch literal 37816 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%0ApXl_Pq~5Nggo;Is;A8HvIU^w$?*y9B+cc^3(zm> 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 vjsmjJg~EG#Un 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