From b34f44d3e7c1d59920cc20fb5b411b70c417ff90 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 15 Jul 2026 11:32:36 +0800 Subject: [PATCH] =?UTF-8?q?perf(html):=20escape=5Fhtml=20=E9=93=BE?= =?UTF-8?q?=E5=BC=8F=205=20=E6=AC=A1=20replace=20=E6=94=B9=E5=8D=95?= =?UTF-8?q?=E9=81=8D=E6=89=AB=E6=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧实现链式 5 次 str::replace,每次全量扫描 + 分配新 String, 5 个特殊字符 = 5 次堆分配 + 5 遍全文扫描,中间 4 个 String 立即丢弃。 新实现:先按字节快速检测是否含特殊字符(纯文本直接 clone 返回), 否则单遍 match 扫描 + 单次 with_capacity 分配。 分配 5→1(-80%),扫描 5→1。该函数被 markdown 渲染管线密集调用 (每个标题文本、每个 runnable 代码块的 overrides/source 转义)。 --- src/utils/html.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/utils/html.rs b/src/utils/html.rs index cbca198..ef2549f 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -9,13 +9,30 @@ /// /// 单引号统一为 `'`(HTML5 规范,与原 server 端 `helpers::escape_html` 一致)。 /// 可安全用于文本节点与属性值上下文。 +/// +/// 单遍扫描:旧实现链式调用 5 次 `str::replace`,每次全量扫描 + 分配一个新 String, +/// 5 个特殊字符意味着 5 次堆分配 + 5 遍扫描。单遍 `match` 只扫描一次、只分配一次。 +/// 该函数被 markdown 渲染管线密集调用(每个标题、每个代码块),属于热点路径。 pub fn escape_html(input: &str) -> String { - input - .replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) - .replace('\'', "'") + // 快速路径:无特殊字符直接 clone(大多纯文本走这里,零额外扫描)。 + // memchr 风格的逐字节查找比总是分配 + 遍历更省。 + if !input.as_bytes().iter().any(|&b| { + matches!(b, b'&' | b'<' | b'>' | b'"' | b'\'') + }) { + return input.to_string(); + } + let mut out = String::with_capacity(input.len()); + for c in input.chars() { + match c { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\'' => out.push_str("'"), + _ => out.push(c), + } + } + out } #[cfg(test)]