Skip to content

Commit b538284

Browse files
perf(schema): add update_schema_owned to avoid unnecessary clones
Introduce update_schema_owned function that takes ownership of the input schema value instead of borrowing, eliminating clone operations when the value is already owned by the caller.
1 parent 1cb288d commit b538284

File tree

3 files changed

+165
-3
lines changed

3 files changed

+165
-3
lines changed

src/template.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl<'a> Template<'a> {
7474
}
7575
};
7676

77-
update_schema(&mut default_schema, &schema);
77+
update_schema_owned(&mut default_schema, schema);
7878
let shared = Shared::new(default_schema.clone());
7979

8080
Ok(Template {
@@ -177,7 +177,7 @@ impl<'a> Template<'a> {
177177
///
178178
/// * `schema` - The JSON Value to be merged with the current schema.
179179
pub fn merge_schema_value(&mut self, schema: Value) {
180-
update_schema(&mut self.schema, &schema);
180+
update_schema_owned(&mut self.schema, schema);
181181
}
182182

183183
/// Constructs a new `Template` instance from a file path and MessagePack schema bytes.
@@ -270,7 +270,7 @@ impl<'a> Template<'a> {
270270
return Err(format!("Is not a valid MessagePack data: {}", e));
271271
}
272272
};
273-
update_schema(&mut self.schema, &schema_value);
273+
update_schema_owned(&mut self.schema, schema_value);
274274

275275
Ok(())
276276
}

src/utils.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,31 @@ pub fn update_schema(a: &mut Value, b: &Value) {
102102
a["version"] = VERSION.into();
103103
}
104104

105+
/// Same as update_schema but takes ownership of `b` to avoid clones.
106+
pub fn update_schema_owned(a: &mut Value, b: Value) {
107+
merge_schema_owned(a, b);
108+
109+
// Different environments may ignore or add capitalization in headers
110+
// Note: after merge_schema_owned, b is consumed, so we read from a
111+
let headers = &a["data"]["CONTEXT"]["HEADERS"];
112+
if headers.get("requested-with-ajax").is_some() {
113+
let val = a["data"]["CONTEXT"]["HEADERS"]["requested-with-ajax"].clone();
114+
a["data"]["CONTEXT"]["HEADERS"]["Requested-With-Ajax"] = val.clone();
115+
a["data"]["CONTEXT"]["HEADERS"]["REQUESTED-WITH-AJAX"] = val;
116+
} else if headers.get("Requested-With-Ajax").is_some() {
117+
let val = a["data"]["CONTEXT"]["HEADERS"]["Requested-With-Ajax"].clone();
118+
a["data"]["CONTEXT"]["HEADERS"]["requested-with-ajax"] = val.clone();
119+
a["data"]["CONTEXT"]["HEADERS"]["REQUESTED-WITH-AJAX"] = val;
120+
} else if headers.get("REQUESTED-WITH-AJAX").is_some() {
121+
let val = a["data"]["CONTEXT"]["HEADERS"]["REQUESTED-WITH-AJAX"].clone();
122+
a["data"]["CONTEXT"]["HEADERS"]["requested-with-ajax"] = val.clone();
123+
a["data"]["CONTEXT"]["HEADERS"]["Requested-With-Ajax"] = val;
124+
}
125+
126+
// Update version
127+
a["version"] = VERSION.into();
128+
}
129+
105130
/// Extract same level blocks positions.
106131
///
107132
/// ```text

tests/test-schema-performance.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/// Performance test for large schema processing
2+
/// Measures the time to create a Template with a large schema
3+
4+
#[cfg(not(debug_assertions))]
5+
use std::time::Instant;
6+
7+
/// Generate a large schema with the specified number of keys
8+
#[cfg(not(debug_assertions))]
9+
fn generate_large_schema(num_keys: usize) -> serde_json::Value {
10+
let mut data = serde_json::Map::new();
11+
12+
for i in 0..num_keys {
13+
let key = format!("key_{}", i);
14+
let value = serde_json::json!({
15+
"id": i,
16+
"name": format!("Item {}", i),
17+
"description": format!("This is a detailed description for item number {} with some extra text to make it larger", i),
18+
"tags": ["tag1", "tag2", "tag3"],
19+
"metadata": {
20+
"created": "2024-01-01",
21+
"updated": "2024-12-31",
22+
"author": format!("author_{}", i % 100),
23+
"version": format!("1.{}.0", i % 10)
24+
}
25+
});
26+
data.insert(key, value);
27+
}
28+
29+
serde_json::json!({
30+
"config": {
31+
"infinite_loop_max_bifs": 555000,
32+
"comments": "keep",
33+
"errors": "hide"
34+
},
35+
"inherit": {
36+
"snippets": {},
37+
"declare": {
38+
"any": "*"
39+
},
40+
"params": {},
41+
"locale": {
42+
"current": "en",
43+
"trans": {}
44+
}
45+
},
46+
"data": data
47+
})
48+
}
49+
50+
#[cfg(not(debug_assertions))]
51+
#[test]
52+
fn test_large_schema_performance() {
53+
let schema_sizes = [100, 500, 1000, 2000];
54+
let iterations = 10;
55+
56+
println!("\n=== Large Schema Performance Test ===\n");
57+
println!("{:<10} {:<15} {:<15} {:<15}", "Keys", "Size (KB)", "Time (ms)", "Per key (µs)");
58+
println!("{}", "-".repeat(55));
59+
60+
for num_keys in schema_sizes {
61+
let schema = generate_large_schema(num_keys);
62+
let schema_size = serde_json::to_string(&schema).unwrap().len();
63+
64+
// Warmup
65+
let schema_clone = schema.clone();
66+
let _ = neutralts::Template::from_file_value("tests/obj.ntpl", schema_clone);
67+
68+
// Measure
69+
let mut total_time = 0u128;
70+
for _ in 0..iterations {
71+
let schema_clone = schema.clone();
72+
let start = Instant::now();
73+
let result = neutralts::Template::from_file_value("tests/obj.ntpl", schema_clone);
74+
let elapsed = start.elapsed().as_nanos();
75+
76+
assert!(result.is_ok(), "Template creation failed");
77+
total_time += elapsed;
78+
}
79+
80+
let avg_time_ns = total_time / iterations as u128;
81+
let avg_time_ms = avg_time_ns as f64 / 1_000_000.0;
82+
let per_key_us = (avg_time_ns as f64 / num_keys as f64) / 1000.0;
83+
84+
println!(
85+
"{:<10} {:<15.1} {:<15.2} {:<15.2}",
86+
num_keys,
87+
schema_size as f64 / 1024.0,
88+
avg_time_ms,
89+
per_key_us
90+
);
91+
}
92+
93+
println!();
94+
}
95+
96+
#[cfg(not(debug_assertions))]
97+
#[test]
98+
fn test_schema_merge_performance() {
99+
use neutralts::Template;
100+
101+
let num_keys = 1000;
102+
let schema = generate_large_schema(num_keys);
103+
let schema_str = serde_json::to_string(&schema).unwrap();
104+
105+
println!("\n=== Schema Merge Performance Test ===\n");
106+
println!("Schema size: {} bytes ({:.1} KB)", schema_str.len(), schema_str.len() as f64 / 1024.0);
107+
108+
// Test merge_schema_str (parses JSON)
109+
let iterations = 20;
110+
let mut total_time = 0u128;
111+
112+
for _ in 0..iterations {
113+
let mut template = Template::new().unwrap();
114+
let start = Instant::now();
115+
template.merge_schema_str(&schema_str).unwrap();
116+
total_time += start.elapsed().as_nanos();
117+
}
118+
119+
let avg_time_ms = (total_time / iterations as u128) as f64 / 1_000_000.0;
120+
println!("merge_schema_str avg time: {:.2} ms", avg_time_ms);
121+
122+
// Test merge_schema_value (already parsed)
123+
let mut total_time = 0u128;
124+
125+
for _ in 0..iterations {
126+
let mut template = Template::new().unwrap();
127+
let schema_clone = schema.clone();
128+
let start = Instant::now();
129+
template.merge_schema_value(schema_clone);
130+
total_time += start.elapsed().as_nanos();
131+
}
132+
133+
let avg_time_ms = (total_time / iterations as u128) as f64 / 1_000_000.0;
134+
println!("merge_schema_value avg time: {:.2} ms", avg_time_ms);
135+
136+
println!();
137+
}

0 commit comments

Comments
 (0)