diff --git a/Cargo.lock b/Cargo.lock index bef1d320a6..39c647a7aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3468,6 +3468,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "serde_core", +] + [[package]] name = "snafu" version = "0.7.5" @@ -4323,6 +4332,7 @@ dependencies = [ "sha2", "sha3", "simdutf8", + "smol_str", "snafu 0.8.9", "snap", "strip-ansi-escapes", diff --git a/Cargo.toml b/Cargo.toml index 5bc47686bd..0d18149170 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -246,6 +246,7 @@ serde = { version = "1", features = ["derive"], optional = true } serde_json = { version = "1", default-features = false, optional = true, features = ["std", "raw_value"] } serde_yaml_ng = { version = "0.10.0" } simdutf8 = { version = "0.1.5", optional = true } +smol_str = { version = "0.3", default-features = false, features = ["serde"] } fancy-regex = { version = "0.17", default-features = false, optional = true } sha1 = { version = "0.10", optional = true } sha2 = { version = "0.10", optional = true } diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index cb0cdc3e14..d74576ae16 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -300,6 +300,7 @@ simdutf8,https://github.com/rusticstuff/simdutf8,MIT OR Apache-2.0,Hans Kratz slab,https://github.com/tokio-rs/slab,MIT,Carl Lerche smallvec,https://github.com/servo/rust-smallvec,MIT OR Apache-2.0,The Servo Project Developers +smol_str,https://github.com/rust-lang/rust-analyzer/tree/master/lib/smol_str,MIT OR Apache-2.0,"Aleksey Kladov , Lukas Wirth " snafu,https://github.com/shepmaster/snafu,MIT OR Apache-2.0,Jake Goulding snafu-derive,https://github.com/shepmaster/snafu,MIT OR Apache-2.0,Jake Goulding snap,https://github.com/BurntSushi/rust-snappy,BSD-3-Clause,Andrew Gallant diff --git a/changelog.d/1825.enhancement.md b/changelog.d/1825.enhancement.md new file mode 100644 index 0000000000..1b2f069254 --- /dev/null +++ b/changelog.d/1825.enhancement.md @@ -0,0 +1,3 @@ +Improved performance of VRL programs that read and write event fields. + +authors: thomasqueirozb diff --git a/src/value/keystring.rs b/src/value/keystring.rs index 8e5a1863bc..24008c0b53 100644 --- a/src/value/keystring.rs +++ b/src/value/keystring.rs @@ -2,20 +2,22 @@ use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; +use smol_str::SmolStr; -/// The key type value. This is a simple zero-overhead wrapper set up to make it explicit that -/// object keys are read-only and their underlying type is opaque and may change for efficiency. +/// The key type for event objects. Backed by [`SmolStr`] so that strings up to +/// 22 bytes are stored inline (no heap allocation). Every capture-group name in +/// a typical regex (e.g. "host", "user", "timestamp") fits inline, making +/// `clone()` a plain 24-byte stack copy — no malloc, no atomic ops. #[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] -#[cfg_attr(any(test, feature = "proptest"), derive(proptest_derive::Arbitrary))] #[serde(transparent)] -pub struct KeyString(String); +pub struct KeyString(SmolStr); impl KeyString { /// Convert the key into a boxed slice of bytes (`u8`). #[inline] #[must_use] pub fn into_bytes(self) -> Box<[u8]> { - self.0.into_bytes().into() + self.0.as_bytes().to_vec().into_boxed_slice() } /// Is this string empty? @@ -36,7 +38,7 @@ impl KeyString { #[inline] #[must_use] pub fn as_str(&self) -> &str { - &self.0 + self.0.as_str() } } @@ -48,50 +50,50 @@ impl Display for KeyString { impl AsRef for KeyString { fn as_ref(&self) -> &str { - &self.0 + self.0.as_str() } } impl std::ops::Deref for KeyString { type Target = str; fn deref(&self) -> &str { - &self.0 + self.0.as_str() } } impl std::borrow::Borrow for KeyString { fn borrow(&self) -> &str { - &self.0 + self.0.as_str() } } impl PartialEq for KeyString { fn eq(&self, that: &str) -> bool { - self.0[..].eq(that) + self.0.as_str() == that } } impl From<&str> for KeyString { fn from(s: &str) -> Self { - Self(s.into()) + Self(SmolStr::new(s)) } } impl From for KeyString { fn from(s: String) -> Self { - Self(s) + Self(SmolStr::new(s.as_str())) } } impl From> for KeyString { fn from(s: Cow<'_, str>) -> Self { - Self(s.into()) + Self(SmolStr::new(s.as_ref())) } } impl From for String { fn from(s: KeyString) -> Self { - s.0 + s.0.as_str().to_owned() } } @@ -102,11 +104,24 @@ impl quickcheck::Arbitrary for KeyString { } fn shrink(&self) -> Box> { - let s = self.0.clone(); + let s = self.0.as_str().to_string(); Box::new(s.shrink().map(Into::into)) } } +#[cfg(any(test, feature = "proptest"))] +impl proptest::arbitrary::Arbitrary for KeyString { + type Parameters = (); + type Strategy = proptest::strategy::BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + use proptest::prelude::Strategy; + proptest::arbitrary::any::() + .prop_map(KeyString::from) + .boxed() + } +} + #[cfg(any(test, feature = "lua"))] mod lua { use mlua::prelude::LuaResult; @@ -122,7 +137,7 @@ mod lua { impl IntoLua for KeyString { fn into_lua(self, lua: &Lua) -> LuaResult { - self.0.into_lua(lua) + self.0.as_str().to_owned().into_lua(lua) } } } diff --git a/src/value/value/crud/insert.rs b/src/value/value/crud/insert.rs index 499905081b..5be2f41fb5 100644 --- a/src/value/value/crud/insert.rs +++ b/src/value/value/crud/insert.rs @@ -13,11 +13,10 @@ pub fn insert<'a, T: ValueCollection>( match path_iter.next() { Some(BorrowedSegment::Field(field)) => { if let Some(Value::Object(map)) = value.get_mut_value(key.borrow()) { - insert(map, field.to_string().into(), path_iter, insert_value) + insert(map, field.into(), path_iter, insert_value) } else { let mut map = BTreeMap::new(); - let prev_value = - insert(&mut map, field.to_string().into(), path_iter, insert_value); + let prev_value = insert(&mut map, field.into(), path_iter, insert_value); value.insert_value(key, Value::Object(map)); prev_value }