perf(html): escape_html 链式 5 次 replace 改单遍扫描

旧实现链式 5 次 str::replace,每次全量扫描 + 分配新 String,
5 个特殊字符 = 5 次堆分配 + 5 遍全文扫描,中间 4 个 String 立即丢弃。

新实现:先按字节快速检测是否含特殊字符(纯文本直接 clone 返回),
否则单遍 match 扫描 + 单次 with_capacity 分配。

分配 5→1(-80%),扫描 5→1。该函数被 markdown 渲染管线密集调用
(每个标题文本、每个 runnable 代码块的 overrides/source 转义)。
This commit is contained in:
xfy 2026-07-15 11:32:36 +08:00
parent 604febde98
commit b34f44d3e7

View File

@ -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('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
.replace('\'', "&#x27;")
// 快速路径:无特殊字符直接 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("&amp;"),
'<' => out.push_str("&lt;"),
'>' => out.push_str("&gt;"),
'"' => out.push_str("&quot;"),
'\'' => out.push_str("&#x27;"),
_ => out.push(c),
}
}
out
}
#[cfg(test)]