perf(round27): NextCloud PROPFIND oc:id per-row buffer, contact JSONB write direct-serialize
Two behaviour-preserving allocation cuts (benches/ROUND27.md), each with a
counting-allocator BEFORE/AFTER gate that rolls back if AFTER does not allocate
fewer than BEFORE:
- H1 NextCloud PROPFIND: the streaming page loops built oc:id as a fresh String
per child (format_oc_id -> format!("{:08}{}", id, instance)). Add
format_oc_id_into(&mut buf, id, svc) and compute into one oc_buf reused across
the page (next to the existing href buffer) — 1 String/row -> 0. 998->0
per-row allocs on a 500-row page, 2.16x wall. The write fns still take
Option<&str>, so no signature change; oc:id bytes identical. Scoped to the two
PROPFIND page loops (the hot directory-listing path); REPORT/trashbin deferred.
- P2 contact create/update: bind sqlx::types::Json(&dtos) (Encode runs to_writer
straight into the JSONB buffer) instead of serde_json::to_value(&dtos) + bind,
which built a throwaway Value DOM per JSONB column. Write-side twin of ROUND23
J1. 21->2 allocs, 4.68x wall for a 3-entry column. Behaviour-preserving:
to_value sorts keys and direct serialize keeps struct order, but Postgres
normalizes JSONB key order so the stored value is identical (verified via psql:
'{...alpha...}'::jsonb = '{...struct...}'::jsonb -> t), and reads decode by
field name; the etag comes from the domain entity, not the stored JSONB.
Adds bench_round27_micro. Verified: cargo fmt clean, cargo clippy --features
bench -D warnings clean (real exit), cargo test --lib --features bench = 529
passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
This commit is contained in:
@@ -1610,8 +1610,10 @@ fn build_nc_streaming_propfind(
|
||||
{
|
||||
let mut xml = Writer::new(&mut chunk);
|
||||
// One href buffer reused across the page instead of a fresh
|
||||
// format! String per child (benches/ROUND19.md §M6).
|
||||
// format! String per child (benches/ROUND19.md §M6); likewise
|
||||
// one oc:id buffer (benches/ROUND27.md §H1).
|
||||
let mut href = String::new();
|
||||
let mut oc_buf = String::new();
|
||||
for file in batch.iter() {
|
||||
let dead = dead_props_for(&file.id, &file_deads);
|
||||
// Only the name varies per row — the encoded
|
||||
@@ -1622,8 +1624,14 @@ fn build_nc_streaming_propfind(
|
||||
href.push_str(&child_href_prefix);
|
||||
href.push_str(&urlencoding::encode(&file.name));
|
||||
let fid = nc_id_of(&file_id_map, &file.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
write_file_response(&mut xml, file, &href, (fid, oc_id.as_deref()), &username, &favs, dead)
|
||||
let oc_id: Option<&str> = match fid {
|
||||
Some(id) => {
|
||||
format_oc_id_into(&mut oc_buf, id, file_id_svc);
|
||||
Some(oc_buf.as_str())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
write_file_response(&mut xml, file, &href, (fid, oc_id), &username, &favs, dead)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
}
|
||||
@@ -1675,8 +1683,10 @@ fn build_nc_streaming_propfind(
|
||||
let mut chunk = Vec::with_capacity(batch.len() * 1024);
|
||||
{
|
||||
let mut xml = Writer::new(&mut chunk);
|
||||
// One href buffer reused across the page (benches/ROUND19.md §M6).
|
||||
// One href buffer reused across the page (benches/ROUND19.md
|
||||
// §M6); likewise one oc:id buffer (benches/ROUND27.md §H1).
|
||||
let mut href = String::new();
|
||||
let mut oc_buf = String::new();
|
||||
for sf in batch.iter() {
|
||||
let dead = dead_props_for(&sf.id, &sub_deads);
|
||||
// Collections carry the trailing slash; prefix
|
||||
@@ -1686,8 +1696,14 @@ fn build_nc_streaming_propfind(
|
||||
href.push_str(&urlencoding::encode(&sf.name));
|
||||
href.push('/');
|
||||
let fid = nc_id_of(&sub_id_map, &sf.id);
|
||||
let oc_id = fid.map(|id| format_oc_id(id, file_id_svc));
|
||||
write_folder_response(&mut xml, sf, &href, (fid, oc_id.as_deref()), &username, &favs, quota, dead)
|
||||
let oc_id: Option<&str> = match fid {
|
||||
Some(id) => {
|
||||
format_oc_id_into(&mut oc_buf, id, file_id_svc);
|
||||
Some(oc_buf.as_str())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
write_folder_response(&mut xml, sf, &href, (fid, oc_id), &username, &favs, quota, dead)
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
}
|
||||
@@ -2062,6 +2078,17 @@ pub fn format_oc_id(id: i64, svc: Option<&Arc<NextcloudFileIdService>>) -> Strin
|
||||
}
|
||||
}
|
||||
|
||||
/// Write `oc:id` (`{:08}{instance_id}`) into a caller-provided buffer reused
|
||||
/// across a PROPFIND/REPORT page — the 0-alloc form of [`format_oc_id`] for the
|
||||
/// emit loops, replacing a fresh `String` per child (benches/ROUND27.md §H1).
|
||||
/// Output is byte-identical to `format_oc_id`.
|
||||
pub fn format_oc_id_into(out: &mut String, id: i64, svc: Option<&Arc<NextcloudFileIdService>>) {
|
||||
use std::fmt::Write as _;
|
||||
out.clear();
|
||||
let _ = write!(out, "{id:08}");
|
||||
out.push_str(svc.map(|s| s.instance_id()).unwrap_or("ocnca"));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user