perf(normalizer): use Rc<str> interning to reduce alias resolution allocations (#726)

Replace per-alias heap allocations with reference-counted string interning
throughout the normalizer's alias resolution pipeline:

- Store alias ARM path segments as Vec<Rc<str>> instead of Vec<String>,
  enabling zero-alloc BTreeMap lookups via Rc::clone (refcount bump)
  rather than Value::from() (heap allocation per lookup)

- Pre-compute lowercased short name (short_name_lc: Rc<str>) at
  registry-load time, enabling allocation-free insertion for the
  common case of non-dotted, non-collision alias short names

- Add rc_lowercase() fast-path that skips allocation when the input
  string is already lowercase ASCII (common for ARM property names)

- Update set_nested_lowercased/set_nested_inner/set_nested_in_btree to
  thread Rc<str> through intermediate object construction, avoiding
  String temporaries at every nesting level

- Replace to_ascii_lowercase().starts_with() in is_root_field_collision
  with a zero-alloc byte-level comparison

Measured 27-49% improvement in normalization time across a range of
Azure Policy alias-heavy resource types (293-608 aliases per type).

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-05-18 15:13:11 -05:00
committed by GitHub
parent be3fde7706
commit 3111bf58f2
3 changed files with 82 additions and 33 deletions

View File

@@ -4,14 +4,13 @@
//! Per-alias path resolution: reads values from versioned ARM paths and places //! Per-alias path resolution: reads values from versioned ARM paths and places
//! them at alias short name paths in the normalized output. //! them at alias short name paths in the normalized output.
use alloc::string::String; use crate::Rc;
use crate::Value; use crate::Value;
use super::super::obj_map::remove_element_field; use super::super::obj_map::remove_element_field;
use super::super::obj_map::{ use super::super::obj_map::{
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_remove, collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_insert_rc,
set_nested_lowercased, ObjMap, obj_remove, set_nested_lowercased, ObjMap,
}; };
use super::super::types::ResolvedAliases; use super::super::types::ResolvedAliases;
use super::element_remap::apply_element_remap_precomputed; use super::element_remap::apply_element_remap_precomputed;
@@ -48,12 +47,14 @@ pub fn apply_alias_entries(
if let Some(value) = value { if let Some(value) = value {
let value = normalize_value(&value, &entry.short_name, None); let value = normalize_value(&value, &entry.short_name, None);
let target = if is_root_field_collision(&entry.short_name, &entry.default_path) { if is_root_field_collision(&entry.short_name, &entry.default_path) {
collision_safe_key(&entry.short_name) let target = collision_safe_key(&entry.short_name);
set_nested_lowercased(result, &target, value);
} else if entry.short_name.contains('.') {
set_nested_lowercased(result, &entry.short_name, value);
} else { } else {
entry.short_name.clone() obj_insert_rc(result, Rc::clone(&entry.short_name_lc), value);
}; }
set_nested_lowercased(result, &target, value);
} }
} }
@@ -84,13 +85,13 @@ pub fn apply_alias_entries(
} }
/// Navigate an ARM path using precomputed segments (avoids per-call split). /// Navigate an ARM path using precomputed segments (avoids per-call split).
fn navigate_arm_path_segments(value: &Value, segments: &[String]) -> Option<Value> { fn navigate_arm_path_segments(value: &Value, segments: &[Rc<str>]) -> Option<Value> {
let mut current = value; let mut current = value;
for segment in segments { for segment in segments {
current = current current = current
.as_object() .as_object()
.ok()? .ok()?
.get(&Value::from(segment.as_str()))?; .get(&Value::String(Rc::clone(segment)))?;
} }
Some(current.clone()) Some(current.clone())
} }

View File

@@ -7,7 +7,7 @@
//! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at //! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at
//! the output boundary via [`make_value`]. //! the output boundary via [`make_value`].
use alloc::string::{String, ToString as _}; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -41,6 +41,33 @@ pub fn obj_insert(map: &mut ObjMap, key: &str, val: Value) {
map.insert(Rc::from(key), val); map.insert(Rc::from(key), val);
} }
/// Insert a key-value pair using a pre-allocated `Rc<str>` key.
///
/// Avoids the `Rc::from(key)` heap allocation that [`obj_insert`] performs.
pub fn obj_insert_rc(map: &mut ObjMap, key: Rc<str>, val: Value) {
map.insert(key, val);
}
/// Lowercase a string, returning an `Rc<str>`.
///
/// Both paths allocate an `Rc<str>` (header + string bytes). The fast-path
/// avoids creating an intermediate lowercased `String` when the input is
/// already all-lowercase ASCII.
pub fn rc_lowercase(s: &str) -> Rc<str> {
if s.bytes().all(|b| !b.is_ascii_uppercase()) {
Rc::from(s)
} else {
Rc::from(s.to_ascii_lowercase())
}
}
/// Insert a key-value pair with the key lowercased, using [`rc_lowercase`]
/// for the allocation fast-path.
pub fn obj_insert_lc(map: &mut ObjMap, key: &str, val: Value) {
let lc = rc_lowercase(key);
map.insert(lc, val);
}
/// Check whether a key exists. /// Check whether a key exists.
pub fn obj_contains(map: &ObjMap, key: &str) -> bool { pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
map.contains_key(key) map.contains_key(key)
@@ -112,7 +139,7 @@ pub fn set_nested_lowercased(result: &mut ObjMap, path: &str, value: Value) {
} }
if segments.len() == 1 { if segments.len() == 1 {
if let Some(&seg) = segments.first() { if let Some(&seg) = segments.first() {
obj_insert(result, &seg.to_ascii_lowercase(), value); obj_insert_lc(result, seg, value);
} }
return; return;
} }
@@ -144,28 +171,28 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
}; };
if segments.len() == 1 { if segments.len() == 1 {
let key = if lowercase { let key: Rc<str> = if lowercase {
first.to_ascii_lowercase() rc_lowercase(first)
} else { } else {
first.to_string() Rc::from(first)
}; };
obj_insert(obj, &key, value); obj_insert_rc(obj, key, value);
return; return;
} }
let seg = if lowercase { let seg: Rc<str> = if lowercase {
first.to_ascii_lowercase() rc_lowercase(first)
} else { } else {
first.to_string() Rc::from(first)
}; };
// Ensure an intermediate object exists at `seg`. // Ensure an intermediate object exists at `seg`.
if !obj_contains(obj, &seg) { if !obj.contains_key(&*seg) {
obj_insert(obj, &seg, make_value(new_map())); obj_insert_rc(obj, Rc::clone(&seg), make_value(new_map()));
} }
// Descend directly into the BTreeMap, avoiding ObjMap round-trip. // Descend directly into the BTreeMap, avoiding ObjMap round-trip.
if let Some(Value::Object(inner_rc)) = obj_get_mut(obj, &seg) { if let Some(Value::Object(inner_rc)) = obj.get_mut(&*seg) {
let inner_btree = Rc::make_mut(inner_rc); let inner_btree = Rc::make_mut(inner_rc);
set_nested_in_btree( set_nested_in_btree(
inner_btree, inner_btree,
@@ -191,12 +218,12 @@ pub fn set_nested_in_btree(
return; return;
}; };
let key_str: String = if lowercase { let key_rc: Rc<str> = if lowercase {
first.to_ascii_lowercase() rc_lowercase(first)
} else { } else {
first.to_string() Rc::from(first)
}; };
let key_val = Value::String(Rc::from(key_str.as_str())); let key_val = Value::String(Rc::clone(&key_rc));
if segments.len() == 1 { if segments.len() == 1 {
btree.insert(key_val, value); btree.insert(key_val, value);
@@ -243,13 +270,24 @@ pub const ROOT_FIELDS: &[&str] = &[
"extendedLocation", "extendedLocation",
]; ];
const PROPERTIES_DOT: &[u8] = b"properties.";
/// Check whether an alias short name collides with a reserved ARM root field /// Check whether an alias short name collides with a reserved ARM root field
/// and needs a collision-safe key. /// and needs a collision-safe key.
pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool { pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool {
ROOT_FIELDS ROOT_FIELDS
.iter() .iter()
.any(|f| f.eq_ignore_ascii_case(short_name)) .any(|f| f.eq_ignore_ascii_case(short_name))
&& default_path.to_ascii_lowercase().starts_with("properties.") && default_path.len() > PROPERTIES_DOT.len()
&& default_path
.as_bytes()
.get(..PROPERTIES_DOT.len())
.is_some_and(|prefix| {
prefix
.iter()
.zip(PROPERTIES_DOT)
.all(|(a, b)| a.to_ascii_lowercase() == *b)
})
} }
/// Return a collision-safe key for an alias whose short name collides with a /// Return a collision-safe key for an alias whose short name collides with a

View File

@@ -18,6 +18,8 @@ use alloc::vec::Vec;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use crate::Rc;
// ─── Top-level response wrappers ──────────────────────────────────────────── // ─── Top-level response wrappers ────────────────────────────────────────────
/// ARM API response envelope: `{ "value": [...] }` /// ARM API response envelope: `{ "value": [...] }`
@@ -404,11 +406,13 @@ pub struct ResolvedEntry {
// ── Precomputed fields (derived at registry-load time) ────────────── // ── Precomputed fields (derived at registry-load time) ──────────────
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias). /// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
pub is_wildcard: bool, pub is_wildcard: bool,
/// Pre-lowercased short name as `Rc<str>` for allocation-free common-case inserts.
pub(crate) short_name_lc: Rc<str>,
/// Precomputed `default_path.split('.').collect()` for fast ARM path navigation. /// Precomputed `default_path.split('.').collect()` for fast ARM path navigation.
pub default_path_segments: Vec<String>, pub(crate) default_path_segments: Vec<Rc<str>>,
/// Precomputed path segments for each versioned path, in the same order /// Precomputed path segments for each versioned path, in the same order
/// as `versioned_paths`. /// as `versioned_paths`.
pub versioned_path_segments: Vec<Vec<String>>, pub(crate) versioned_path_segments: Vec<Vec<Rc<str>>>,
} }
impl ResolvedEntry { impl ResolvedEntry {
@@ -420,10 +424,15 @@ impl ResolvedEntry {
metadata: Option<AliasPathMetadata>, metadata: Option<AliasPathMetadata>,
) -> Self { ) -> Self {
let is_wildcard = short_name.contains("[*]"); let is_wildcard = short_name.contains("[*]");
let default_path_segments = default_path.split('.').map(String::from).collect(); let short_name_lc = if short_name.bytes().all(|b| !b.is_ascii_uppercase()) {
Rc::from(short_name.as_str())
} else {
Rc::from(short_name.to_ascii_lowercase())
};
let default_path_segments = default_path.split('.').map(Rc::from).collect();
let versioned_path_segments = versioned_paths let versioned_path_segments = versioned_paths
.iter() .iter()
.map(|(_, p)| p.split('.').map(String::from).collect()) .map(|(_, p)| p.split('.').map(Rc::from).collect())
.collect(); .collect();
Self { Self {
short_name, short_name,
@@ -431,6 +440,7 @@ impl ResolvedEntry {
versioned_paths, versioned_paths,
metadata, metadata,
is_wildcard, is_wildcard,
short_name_lc,
default_path_segments, default_path_segments,
versioned_path_segments, versioned_path_segments,
} }
@@ -456,7 +466,7 @@ impl ResolvedEntry {
/// Returns the versioned segments if `api_version` matches, otherwise /// Returns the versioned segments if `api_version` matches, otherwise
/// the default segments. This avoids per-call `split('.')` for both /// the default segments. This avoids per-call `split('.')` for both
/// default and versioned scalar alias navigation. /// default and versioned scalar alias navigation.
pub fn select_path_segments(&self, api_version: Option<&str>) -> &[String] { pub(crate) fn select_path_segments(&self, api_version: Option<&str>) -> &[Rc<str>] {
if let Some(ver) = api_version { if let Some(ver) = api_version {
for (i, (v, _)) in self.versioned_paths.iter().enumerate() { for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
if v.eq_ignore_ascii_case(ver) { if v.eq_ignore_ascii_case(ver) {