Skip to content

perf(chat): batch message height memory so long topics load faster - #227

Merged
lioensky merged 8 commits into
lioensky:mainfrom
RoxyAsahi:pr/history-height-batch
Oct 3, 2026
Merged

lioensky merged 8 commits into
lioensky:mainfrom
RoxyAsahi:pr/history-height-batch

Conversation

@RoxyAsahi

@RoxyAsahi RoxyAsahi commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

改动

长话题注册消息和延迟扫描时,逐条读 offsetHeight、写 containIntrinsicSize 容易产生重复同步布局。改为下一动画帧统一读完高度,再统一写入占位;暂停和恢复仍使用现有同步路径,保留原消息 DOM 和 content-visibility。

异步任务随优化器生命周期结束:取消观察时移出队列;初始化换根和销毁时清队列、取消已排定帧或回退定时器并更换代次。迟到的旧回调不会消费新任务。写入阶段再次校验连接及归属,可选高度桥接回调重入换根时放弃旧批次剩余数据。

已同步本轮上游 6da3c4d3(包含已合并的 #225)。相对它仅涉及优化器、批处理测试和测试文件白名单,共 3 个文件。没有全量预热、平均高度估算、虚拟列表或渲染流程改写;本 PR 优化加载调度,不宣称解决上游滚动跳动。

对抗式检查

  • 上一版 4e82b2c3 的三个攻击用例失败:销毁没有取消待执行帧、换根继续依赖旧帧、可选桥接回调取消后续消息观察后仍继续写入。本轮 a903a038 修复。
  • 真实 Electron 中,移除排定动画帧的旧 iframe,再把同一优化器初始化到新根:旧版新根未异步记住高度,修补版正常。该实验屏蔽 IntersectionObserver 回调,以免同步暂停恢复测高掩盖异步队列故障。
  • 回归覆盖旧回调晚到、销毁后重新初始化、换根、消息转移、写入重入、下一批入队、单项读取异常及动画帧/定时器回退取消。优化器测试 16/16。
  • 真实 DOM 的帧前正文变高、测试图片解码后的延迟扫描测高,候选版通过;不等价于所有插件媒体验收。

完整应用对比

两个独立完整检出及测试历史副本,相同 Electron、本地流式服务、1200×800 视口,交替运行。基线 6da3c4d3,候选为它加本 PR。

时间为进程内页面重新加载至最后一条消息 DOM 插入,不是冷启动或离屏正文全部完成渲染。

话题 每版次数 上游 本 PR
94 条真实消息副本 3 中位数 3.19 秒 中位数 2.65 秒
20 条真实消息副本 1 2.60 秒 2.36 秒
1000 条合成消息 1 50.95 秒 30.13 秒

94 条首轮候选 4.01 秒、上游 3.86 秒;不保证每次更快。样本量有限。两版均插入预期消息数量并记录高度,未记录页面错误;94 条初始总高度同为 22985px,1000 条同为 132692px。

验证与限制

  • 优化器及导航相关合并执行 38/38;test:chat-kernel 194/194。部分文件重叠,不将数字累加。
  • 94、20、1000 条加载及真实滚轮上翻。最终位置:94 条一致,20 条差 2px,1000 条差 1px;布局位移诊断未显示新增更大的事件。
  • 94 条宽度从 1200px 调到 960px 后上翻,两版最终位置和高度一致。
  • 1000 条受控逐帧压力检查:先用真实滚轮释放贴底,再向上 instant scroll 400px 共 60 次,每次原生文本 Range 采样 8 帧。两版均有 55 个有效锚点,最大坐标残差 245.23px、3 个步骤超过 20px;最大高度补偿均为 12386.5px,最终位置和高度一致。上游跳动仍在;高度补偿值不直接等同视觉跳动。
  • 快速切换 20→94→20,跨消息 DOM 选择。
  • 本地完整流式回复结束、输入清空、跟随底部;输出期间滚轮上翻后保持阅读位置,两版所读消息位移 0px。
  • 语法、diff 空白、设计边界、VCPUI consumer 门禁。两版重建事件图后聊天契约通过,生成文件无内容差异且未提交。

现有门禁失败已在本轮上游复现:next-delta 缺少 preloads/shared/catalog.js;UI 门禁报 settings/group-slots.js 内联样式;聊天 consumer 门禁报 settingsManager.js 选择状态所有权断言。不将它们计作通过。

隐藏窗口或繁忙主线程可能延迟动画帧,这是调度方案的时序代价。插件完整矩阵、原生 Ctrl+F、系统剪贴板跨消息复制和其他机器尚未全面覆盖;有限测试不保证所有环境零缺陷。滚动探针数据不作为标准 CLS。

… forcing a layout per message

Opening a 94-message real topic spent about half its load time in forced
synchronous layout. observeMessage and the delayed scan each read
offsetHeight and then wrote containIntrinsicSize, one message at a time,
so every read after the first write laid the page out again.

Registration and the delayed scan now queue the message instead. One
animation frame later every queued message is measured first, and only
then are the heights written. Pause and resume keep the synchronous path
because they need the value at once.

Measured on a copy of that history: load went from 7.0s to 3.9-4.6s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RoxyAsahi
RoxyAsahi marked this pull request as ready for review October 2, 2026 18:21
@RoxyAsahi
RoxyAsahi marked this pull request as draft October 3, 2026 00:26
@RoxyAsahi
RoxyAsahi marked this pull request as ready for review October 3, 2026 12:17
@lioensky
lioensky merged commit eddfbb9 into lioensky:main Oct 3, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants