perf(data): inline numeric factor keys and preserve borrowed static names
This commit is contained in:
@@ -4411,7 +4411,7 @@ fn normalize_factor_snapshots(
|
|||||||
{
|
{
|
||||||
Some((field, value))
|
Some((field, value))
|
||||||
} else {
|
} else {
|
||||||
Some((Cow::Owned(trimmed.to_ascii_lowercase()), value))
|
Some((CompactString::from(trimmed.to_ascii_lowercase()), value))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
|
||||||
|
use compact_str::CompactString;
|
||||||
use serde::de::{MapAccess, Visitor};
|
use serde::de::{MapAccess, Visitor};
|
||||||
use serde::ser::SerializeMap;
|
use serde::ser::SerializeMap;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@@ -10,7 +11,14 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
/// Sorted numeric fields stored contiguously, without a tree node per snapshot.
|
/// Sorted numeric fields stored contiguously, without a tree node per snapshot.
|
||||||
#[derive(Clone, Default, PartialEq)]
|
#[derive(Clone, Default, PartialEq)]
|
||||||
pub struct NumericFactorMap {
|
pub struct NumericFactorMap {
|
||||||
entries: Vec<(Cow<'static, str>, f64)>,
|
entries: Vec<(CompactString, f64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compact_key(key: Cow<'static, str>) -> CompactString {
|
||||||
|
match key {
|
||||||
|
Cow::Borrowed(value) => CompactString::const_new(value),
|
||||||
|
Cow::Owned(value) => CompactString::from(value),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NumericFactorMap {
|
impl NumericFactorMap {
|
||||||
@@ -49,6 +57,10 @@ impl NumericFactorMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, key: Cow<'static, str>, value: f64) -> Option<f64> {
|
pub fn insert(&mut self, key: Cow<'static, str>, value: f64) -> Option<f64> {
|
||||||
|
self.insert_compact(compact_key(key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_compact(&mut self, key: CompactString, value: f64) -> Option<f64> {
|
||||||
if self
|
if self
|
||||||
.entries
|
.entries
|
||||||
.last()
|
.last()
|
||||||
@@ -76,14 +88,14 @@ impl NumericFactorMap {
|
|||||||
.map(|index| self.entries.remove(index).1)
|
.map(|index| self.entries.remove(index).1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain(&mut self, mut keep: impl FnMut(&Cow<'static, str>, &mut f64) -> bool) {
|
pub fn retain(&mut self, mut keep: impl FnMut(&CompactString, &mut f64) -> bool) {
|
||||||
self.entries.retain_mut(|(key, value)| keep(key, value));
|
self.entries.retain_mut(|(key, value)| keep(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> Iter<'_> {
|
pub fn iter(&self) -> Iter<'_> {
|
||||||
Iter(self.entries.iter())
|
Iter(self.entries.iter())
|
||||||
}
|
}
|
||||||
pub fn keys(&self) -> impl DoubleEndedIterator<Item = &Cow<'static, str>> + ExactSizeIterator {
|
pub fn keys(&self) -> impl DoubleEndedIterator<Item = &CompactString> + ExactSizeIterator {
|
||||||
self.entries.iter().map(|(key, _)| key)
|
self.entries.iter().map(|(key, _)| key)
|
||||||
}
|
}
|
||||||
pub fn values(&self) -> impl DoubleEndedIterator<Item = &f64> + ExactSizeIterator {
|
pub fn values(&self) -> impl DoubleEndedIterator<Item = &f64> + ExactSizeIterator {
|
||||||
@@ -104,9 +116,9 @@ impl Index<&str> for NumericFactorMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Iter<'a>(std::slice::Iter<'a, (Cow<'static, str>, f64)>);
|
pub struct Iter<'a>(std::slice::Iter<'a, (CompactString, f64)>);
|
||||||
impl<'a> Iterator for Iter<'a> {
|
impl<'a> Iterator for Iter<'a> {
|
||||||
type Item = (&'a Cow<'static, str>, &'a f64);
|
type Item = (&'a CompactString, &'a f64);
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.0.next().map(|(k, v)| (k, v))
|
self.0.next().map(|(k, v)| (k, v))
|
||||||
}
|
}
|
||||||
@@ -121,14 +133,14 @@ impl DoubleEndedIterator for Iter<'_> {
|
|||||||
}
|
}
|
||||||
impl ExactSizeIterator for Iter<'_> {}
|
impl ExactSizeIterator for Iter<'_> {}
|
||||||
impl<'a> IntoIterator for &'a NumericFactorMap {
|
impl<'a> IntoIterator for &'a NumericFactorMap {
|
||||||
type Item = (&'a Cow<'static, str>, &'a f64);
|
type Item = (&'a CompactString, &'a f64);
|
||||||
type IntoIter = Iter<'a>;
|
type IntoIter = Iter<'a>;
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl IntoIterator for NumericFactorMap {
|
impl IntoIterator for NumericFactorMap {
|
||||||
type Item = (Cow<'static, str>, f64);
|
type Item = (CompactString, f64);
|
||||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.entries.into_iter()
|
self.entries.into_iter()
|
||||||
@@ -137,6 +149,11 @@ impl IntoIterator for NumericFactorMap {
|
|||||||
|
|
||||||
impl FromIterator<(Cow<'static, str>, f64)> for NumericFactorMap {
|
impl FromIterator<(Cow<'static, str>, f64)> for NumericFactorMap {
|
||||||
fn from_iter<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(iter: T) -> Self {
|
||||||
|
iter.into_iter().map(|(key, value)| (compact_key(key), value)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromIterator<(CompactString, f64)> for NumericFactorMap {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (CompactString, f64)>>(iter: T) -> Self {
|
||||||
let mut entries: Vec<_> = iter.into_iter().collect();
|
let mut entries: Vec<_> = iter.into_iter().collect();
|
||||||
// Stable sorting preserves last-value-wins for repeated input keys.
|
// Stable sorting preserves last-value-wins for repeated input keys.
|
||||||
if !entries.windows(2).all(|pair| pair[0].0 <= pair[1].0) {
|
if !entries.windows(2).all(|pair| pair[0].0 <= pair[1].0) {
|
||||||
@@ -155,6 +172,11 @@ impl FromIterator<(Cow<'static, str>, f64)> for NumericFactorMap {
|
|||||||
}
|
}
|
||||||
impl Extend<(Cow<'static, str>, f64)> for NumericFactorMap {
|
impl Extend<(Cow<'static, str>, f64)> for NumericFactorMap {
|
||||||
fn extend<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (Cow<'static, str>, f64)>>(&mut self, iter: T) {
|
||||||
|
self.extend(iter.into_iter().map(|(key, value)| (compact_key(key), value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Extend<(CompactString, f64)> for NumericFactorMap {
|
||||||
|
fn extend<T: IntoIterator<Item = (CompactString, f64)>>(&mut self, iter: T) {
|
||||||
let mut incoming: Self = iter.into_iter().collect();
|
let mut incoming: Self = iter.into_iter().collect();
|
||||||
if incoming.is_empty() {
|
if incoming.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -194,9 +216,7 @@ impl<const N: usize> From<[(Cow<'static, str>, f64); N]> for NumericFactorMap {
|
|||||||
}
|
}
|
||||||
impl From<BTreeMap<Cow<'static, str>, f64>> for NumericFactorMap {
|
impl From<BTreeMap<Cow<'static, str>, f64>> for NumericFactorMap {
|
||||||
fn from(entries: BTreeMap<Cow<'static, str>, f64>) -> Self {
|
fn from(entries: BTreeMap<Cow<'static, str>, f64>) -> Self {
|
||||||
Self {
|
entries.into_iter().collect()
|
||||||
entries: entries.into_iter().collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +239,8 @@ impl<'de> Deserialize<'de> for NumericFactorMap {
|
|||||||
}
|
}
|
||||||
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
while let Some((key, value)) = map.next_entry::<String, f64>()? {
|
while let Some((key, value)) = map.next_entry::<CompactString, f64>()? {
|
||||||
entries.push((Cow::Owned(key), value));
|
entries.push((key, value));
|
||||||
}
|
}
|
||||||
Ok(entries.into_iter().collect())
|
Ok(entries.into_iter().collect())
|
||||||
}
|
}
|
||||||
@@ -233,6 +253,34 @@ impl<'de> Deserialize<'de> for NumericFactorMap {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_keys_inline_dynamic_names_and_keep_long_static_storage() {
|
||||||
|
const LONG: &str = "a_long_static_factor_identifier_that_must_remain_borrowed";
|
||||||
|
let map = NumericFactorMap::from([
|
||||||
|
(Cow::Owned("dynamic_factor_20".to_owned()), -0.0),
|
||||||
|
(Cow::Borrowed(LONG), 1.0),
|
||||||
|
]);
|
||||||
|
let cloned = map.clone();
|
||||||
|
let short = cloned.keys().find(|key| key.as_str() == "dynamic_factor_20").unwrap();
|
||||||
|
assert!(!short.is_heap_allocated());
|
||||||
|
let long = cloned.keys().find(|key| key.as_str() == LONG).unwrap();
|
||||||
|
assert_eq!(long.as_static_str(), Some(LONG));
|
||||||
|
assert_eq!(cloned["dynamic_factor_20"].to_bits(), (-0.0_f64).to_bits());
|
||||||
|
assert_eq!(std::mem::size_of::<(CompactString, f64)>(), std::mem::size_of::<(Cow<'static, str>, f64)>());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn long_dynamic_unicode_and_short_keys_keep_the_same_json_map() {
|
||||||
|
let entries = ["", "a", "a_field_longer_than_the_inline_string_capacity", "价格因子", "ths_up_days_stock"]
|
||||||
|
.into_iter().enumerate().map(|(index, key)| (Cow::Owned(key.to_string()), index as f64 + 0.25))
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
let map = NumericFactorMap::from(entries.clone());
|
||||||
|
assert_eq!(serde_json::to_string(&map).unwrap(), serde_json::to_string(&entries).unwrap());
|
||||||
|
let decoded: NumericFactorMap = serde_json::from_str(&serde_json::to_string(&map).unwrap()).unwrap();
|
||||||
|
assert_eq!(decoded, map);
|
||||||
|
assert!(!decoded.keys().find(|key| key.as_str() == "ths_up_days_stock").unwrap().is_heap_allocated());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn updates_order_removal_and_values_match_tree_map() {
|
fn updates_order_removal_and_values_match_tree_map() {
|
||||||
let mut flat = NumericFactorMap::new();
|
let mut flat = NumericFactorMap::new();
|
||||||
@@ -330,10 +378,11 @@ mod tests {
|
|||||||
flat.iter().collect::<Vec<_>>(),
|
flat.iter().collect::<Vec<_>>(),
|
||||||
tree.iter().collect::<Vec<_>>()
|
tree.iter().collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
assert!(matches!(flat.keys().last(), Some(Cow::Borrowed("shared"))));
|
assert_eq!(flat.keys().last().map(CompactString::as_str), Some("shared"));
|
||||||
|
assert!(!flat.keys().last().unwrap().is_heap_allocated());
|
||||||
flat.extend([(Cow::Borrowed("zz"), f64::NAN)]);
|
flat.extend([(Cow::Borrowed("zz"), f64::NAN)]);
|
||||||
assert!(flat["zz"].is_nan());
|
assert!(flat["zz"].is_nan());
|
||||||
flat.extend(std::iter::empty());
|
flat.extend(std::iter::empty::<(CompactString, f64)>());
|
||||||
assert_eq!(flat.len(), tree.len() + 1);
|
assert_eq!(flat.len(), tree.len() + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user