perf: round 18 — calendar-event in-place iCal edit, ResourceList incremental id-index

Two items from the ROUND17 deferred list, each benchmark-gated with a
BEFORE/AFTER equivalence gate and a rollback-on-regression check (ROUND2–17
discipline). See benches/ROUND18.md.

[C1] backend — CalendarEvent::update_ical_property / remove_ical_property
rewrote the ENTIRE ical_data body with format!("{}{}{}") on every call and
allocated two search needles per call. calendar_storage_adapter::update_event
fans a multi-field edit out into one call per changed field, so a full REST
edit paid one full-body (up to ~11 KB) allocation per property. The body is
now mutated in place (replace_range for an existing property, four insert/
insert_str for a new one, byte-identical spans) and the single "\nNAME:"
needle is built on the stack (the "\r\nNAME:" needle was redundant — the LF
form is its suffix). bench_round18_micro [C1]: 70 -> 2 allocs/op (68 fewer),
2.46x wall, emitted body byte-identical.

[F1] frontend — ResourceList itemIndexById rebuilt a fresh Map over the whole
accumulated list every infinite-scroll page (O(N)/page, O(N^2) drain) and,
being a new instance each page, re-fired the reap-stale effect (another O(N)
id Set/page). New ItemIndexBuilder extends a persistent Map with the fresh
page only and reuses the reference across appends; the reap-stale effect now
tests membership against it. round18.bench.test.ts [F1]: 40x50 drain 74.1 ->
6.4 ms (11.5x), deep-equal to the reference at every page, reference-contract
gate (same-ref append / new-ref rebuild).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FoxFtikahM1N4PE5s3ZVH3
This commit is contained in:
Claude
2026-07-19 20:53:30 +00:00
parent ba2245a524
commit 6bc09cb3b3
7 changed files with 837 additions and 53 deletions
+69 -40
View File
@@ -1092,40 +1092,70 @@ impl CalendarEvent {
* @param property_name The name of the property to update
* @param value The new value for the property
*/
/// Write `\n{name}:` into `buf` and return it as `&str`, or `None` if the
/// name is too long to fit (unreachable for RFC 5545 property names, the
/// longest of which — `LAST-MODIFIED`, `RECURRENCE-ID` — are 13 bytes).
///
/// The bare-LF form is deliberate: `\n{name}:` is a suffix of the CRLF form
/// `\r\n{name}:`, so a single search for it matches a property line whether
/// the body is LF- or CRLF-terminated and returns the LF offset either way
/// — behaviour-identical to the old `find("\n..").or(find("\r\n.."))` (the
/// CRLF needle could never match where the LF one didn't). Built on the
/// stack: no per-call heap needle.
fn line_needle<'a>(buf: &'a mut [u8; 64], name: &str) -> Option<&'a str> {
let n = name.len();
if n + 2 > buf.len() {
return None;
}
buf[0] = b'\n';
buf[1..1 + n].copy_from_slice(name.as_bytes());
buf[1 + n] = b':';
// `name` is valid UTF-8 and only ASCII bytes were added around it.
std::str::from_utf8(&buf[..n + 2]).ok()
}
fn update_ical_property(&mut self, property_name: &str, value: &str) {
let search_str = format!("\n{}:", property_name);
let search_str_alt = format!("\r\n{}:", property_name);
let mut buf = [0u8; 64];
let needle_owned;
let needle: &str = match Self::line_needle(&mut buf, property_name) {
Some(n) => n,
None => {
needle_owned = format!("\n{property_name}:");
&needle_owned
}
};
// Check if property exists
let pos = self
.ical_data
.find(&search_str)
.or_else(|| self.ical_data.find(&search_str_alt));
if let Some(pos) = pos {
// Find the start of the value
let value_start = pos + search_str.len();
// Find the end of the value (next line or end of string)
if let Some(pos) = self.ical_data.find(needle) {
// Value spans from just after `\n{NAME}:` to the next LF (or EOF).
let value_start = pos + needle.len();
let value_end = self.ical_data[value_start..]
.find('\n')
.map(|p| value_start + p)
.unwrap_or_else(|| self.ical_data.len());
.map_or(self.ical_data.len(), |p| value_start + p);
// Replace the value
let before = &self.ical_data[..value_start];
let after = &self.ical_data[value_end..];
self.ical_data = format!("{}{}{}", before, value, after);
// In place: reuses the body's own buffer (growing it once only when
// the new value is longer) instead of allocating a whole fresh body
// String per property, as the old `format!("{}{}{}")` did — on a
// multi-field edit that was one full-body (up to ~11 KB) allocation
// per changed property.
self.ical_data.replace_range(value_start..value_end, value);
} else {
// Property doesn't exist, add it before END:VEVENT
// Property absent: insert `{NAME}:{value}\n` before END:VEVENT.
let end_pos = self
.ical_data
.find("END:VEVENT")
.unwrap_or(self.ical_data.len());
let before = &self.ical_data[..end_pos];
let after = &self.ical_data[end_pos..];
self.ical_data = format!("{}{}:{}\n{}", before, property_name, value, after);
// Insert the four pieces at one point in reverse order so the result
// is `{NAME}:{value}\n` before END:VEVENT — byte-identical to the old
// `format!("{}{}:{}\n{}")` — without allocating a fresh body String
// (nor a value-sized fragment). One `reserve` caps it at a single
// grow; the shifted tail is just the trailing END:VEVENT/VCALENDAR.
self.ical_data
.reserve(property_name.len() + value.len() + 2);
self.ical_data.insert(end_pos, '\n');
self.ical_data.insert_str(end_pos, value);
self.ical_data.insert(end_pos, ':');
self.ical_data.insert_str(end_pos, property_name);
}
}
@@ -1135,26 +1165,25 @@ impl CalendarEvent {
* @param property_name The name of the property to remove
*/
fn remove_ical_property(&mut self, property_name: &str) {
let search_str = format!("\n{}:", property_name);
let search_str_alt = format!("\r\n{}:", property_name);
let mut buf = [0u8; 64];
let needle_owned;
let needle: &str = match Self::line_needle(&mut buf, property_name) {
Some(n) => n,
None => {
needle_owned = format!("\n{property_name}:");
&needle_owned
}
};
// Check if property exists
let pos = self
.ical_data
.find(&search_str)
.or_else(|| self.ical_data.find(&search_str_alt));
if let Some(pos) = pos {
// Find the end of the value (next line or end of string)
if let Some(pos) = self.ical_data.find(needle) {
// Delete from the property's leading LF (`pos`) through the end of
// its value line (exclusive of the next line's LF) — byte-identical
// to the old `format!("{}{}", &data[..pos], &data[value_end..])`,
// but in place, with no fresh body String.
let value_end = self.ical_data[pos + 1..]
.find('\n')
.map(|p| pos + 1 + p)
.unwrap_or_else(|| self.ical_data.len());
// Remove the property
let before = &self.ical_data[..pos];
let after = &self.ical_data[value_end..];
self.ical_data = format!("{}{}", before, after);
.map_or(self.ical_data.len(), |p| pos + 1 + p);
self.ical_data.replace_range(pos..value_end, "");
}
}
}