鸿蒙 PC Markdown 编辑器性能工程:中文输入与 10MiB 文档

鸿蒙 PC Markdown 编辑器性能工程:中文输入与 10MiB 文档

本文讨论中文 IME 正确性、固定语料、加载时间、完整进程组 PSS 和大文件功能降级。完整示例代码:https://gitcode.com/VON-/codex_md_oh

编辑器性能要同时看时间、内存与正确性

鸿蒙 PC Markdown 编辑器的性能不能只用"打开很快"来判断。大文档可能在加载时很快,但在撤销、预览或 IME 组合输入时消耗大量内存。因此当前测试同时覆盖:

  • 1MiB 与 10MiB UTF-8 Markdown 文档的读取和编辑器加载时间。
  • 主进程、ArkWeb 活跃进程和 ArkWeb 辅助进程的完整进程组 PSS。
  • 中文输入、撤销、重做、保存快照与修改状态。
  • 大文档连续撤销时是否出现异常退出或 LowMemoryKill。

5MiB 阈值后主动降级

当文档字符数达到 5×1024×1024,编辑器切换到大文件模式:

ts 复制代码
const LARGE_DOCUMENT_CHARACTER_THRESHOLD = 5 * 1024 * 1024;

function updateLargeDocumentMode(documentLength: number): void {
  largeDocumentMode = documentLength >= LARGE_DOCUMENT_CHARACTER_THRESHOLD;
  if (largeDocumentMode && currentMode !== 'source') {
    currentMode = 'source';
    workspace.dataset.mode = currentMode;
    previewDirty = true;
  }
}

function createEditorState(content: string): EditorState {
  const useLargeDocumentSetup = content.length >= LARGE_DOCUMENT_CHARACTER_THRESHOLD;
  const lineSeparator = content.includes('\r\n') ? '\r\n' : '\n';
  return EditorState.create({
    doc: content,
    extensions: [
      useLargeDocumentSetup ? minimalSetup : basicSetup,
      useLargeDocumentSetup ? [] : markdown({ base: markdownLanguage }),
      EditorState.lineSeparator.of(lineSeparator),
      placeholder('Start writing Markdown...'),
      useLargeDocumentSetup ? [] : EditorView.lineWrapping,
      EditorView.updateListener.of((update) => {
        if (!update.docChanged) {
          return;
        }

        documentRevision += 1;
        pendingDirty = forcedDirty || !update.state.doc.eq(baselineDocument);
        updateLargeDocumentMode(update.state.doc.length);
        if (currentMode === 'source') {
          previewDirty = true;
        } else {
          renderPreview(update.state.sliceDoc());
        }
        notifyNative(update.state.doc.length);
        scheduleRecoverySnapshot();
      }),
      EditorView.theme({
        '&': { height: '100%' },
        '.cm-scroller': { overflow: 'auto' },
        '.cm-content': { minHeight: '100%' }
      })
    ]
  });
}

代码来源:web-editor/src/main.ts

降级后使用 minimalSetup,关闭 Markdown 语法扩展、换行和预览模式。这是有意的产品取舍:大文档首先保证能打开、能编辑和能保存,而不是在极限场景中继续运行所有高成本功能。

Release 模拟器实测数据

指标 目标 实测 结论
1MiB 读取 计入 1 秒总预算 48ms 通过
1MiB 编辑器加载 计入 1 秒总预算 35ms 通过
1MiB 总加载 ≤ 1 秒 83ms 通过
10MiB 读取 计入 3 秒总预算 158ms 通过
10MiB 编辑器加载 计入 3 秒总预算 187ms 通过
10MiB 总加载 ≤ 3 秒 345ms 通过
1MiB 完整进程组 PSS < 250MiB 324.7MiB 距目标高 29.9%
10MiB 完整进程组 PSS 建立技术基线 396.4MiB 已记录

内存数据不应被描述为"全部通过"。1MiB 的 324.7MiB 高于 250MiB 正式目标,只是满足预先定义的"距目标不超过 30% 且有优化路径"技术验证容差。

鸿蒙 PC 大文件模式截图

下图为 10MiB Markdown 文档在鸿蒙 PC / 2in1 模拟器中加载后的画面。右上角 Split 和 Preview 不可用,状态栏显示 Large file mode

当前结论

模拟器中,"鸿蒙输入测试"可以输入,撤销后为空,重做后恢复,字数计数为 6。修复前,10MiB 文档连续撤销曾触发一次 LowMemoryKill;修复后相同路径未产生新的异常退出。

这些是可以支持架构继续前进的模拟器证据,但不替代鸿蒙 PC 真机上的多次 Release P95、物理键盘长按、系统快捷键冲突和触控板滚动测试。

中文输入首先是正确性问题

英文键盘事件通常是一次按键对应一次可见字符,中文输入法不是。用户输入拼音时,浏览器会经历 composition start、update、end,候选文本可能多次变化,最终才提交到文档。若编辑器或快捷键处理器把组合过程当成普通按键,就会出现重复字符、候选窗提前关闭、撤销粒度异常或拼音字母被直接写入正文。

因此 IME 验证不能只看最后是否出现汉字,还要检查完整状态:

  1. 组合期间候选文本不触发保存、命令面板等快捷键。
  2. 候选提交后正文只出现一次目标字符。
  3. 一次撤销的粒度符合用户预期,不把组合过程拆成多个残片。
  4. 重做能够恢复完全相同的 Unicode 文本。
  5. 修改标记、字数和恢复快照以提交后的文档为准。
  6. 中文后继续输入标点、英文和换行,光标位置保持正确。

CodeMirror 已经封装了浏览器编辑事件的大量细节,但 ArkWeb 版本、鸿蒙系统 IME 和物理键盘仍会影响实际行为。浏览器自动化可以验证插入与撤销状态,不能完全模拟系统候选窗;真机需要覆盖全拼、双拼、中文标点、长句候选、退格修改和中英文切换。

正确性指标和性能指标不能互相替代

编辑器在 30ms 内显示了错误字符,性能仍然是不合格;输入完全正确但每次按键卡顿 500ms,同样不可用。测试报告应把两类结果分开。

正确性关注内容、选区、撤销、保存和格式。性能关注加载时间、输入延迟、滚动、内存和长时间稳定性。二者的交叉点是降级策略:关闭语法高亮可以改善性能,但不能破坏正文;暂停实时预览可以减少工作量,但必须明确显示当前处于 Source 模式。

性能优化必须守住文档正确性回归。任何改变事务分组、Bridge 节流或大文档扩展的提交,都应重新执行中文输入、撤销重做和保存快照用例,而不能只比较启动时间。

固定语料才能比较版本

"打开一个大文件"无法复现。测试语料至少要固定字节大小、字符结构、最长行、标题密度、代码块比例、行尾和编码。两个同为 10MiB 的文件,性能可能完全不同:一份有十万行短文本,另一份只有一个超长行;软换行、语法解析和 DOM 节点数量会产生不同瓶颈。

建议至少维护以下夹具:

  • 1MiB 常规 Markdown:中英文段落、标题、列表、链接和代码块混合。
  • 10MiB 多行文档:用于大文件加载、滚动、撤销和内存基线。
  • 10MiB 超长行文档:专门观察换行布局和横向滚动。
  • 大量标题文档:观察 Markdown 语法树和未来大纲索引成本。
  • 中文密集文档:比较字节数、UTF-16 长度、字数统计和 IME。
  • CRLF/BOM 文档:确认性能路径没有绕过格式兼容。

夹具生成规则、SHA-256 和实际大小应进入报告。每轮测试使用同一文件,才能判断 345ms 到 420ms 是代码回归还是语料变化。

加载时间要拆分阶段

总加载时间可以直接描述用户等待,但只记录总数很难定位瓶颈。当前测量把文件读取和编辑器加载分开:ArkTS 从授权 URI 分块读取并严格解码,随后 Bridge 把正文设置到 CodeMirror,页面创建 EditorState 和 EditorView。

进一步可以拆成:

text 复制代码
用户确认文件
  → URI open/stat
  → 分块 read + UTF-8 decode
  → 格式检测
  → Bridge 传输
  → EditorState 创建
  → 首次可编辑帧
  → 首次预览完成

"首次可编辑"和"预览完成"不必是同一个时间点。对于较大文档,可以先呈现 Source 并接受输入,再延迟生成预览。测量点必须使用单调时钟,避免系统时间调整;多次运行要区分冷启动、热启动和文件缓存。

正式门槛应采用 P95,而不是挑最快一次。至少记录样本数、中位数、P95、最大值和是否存在首次运行异常。模拟器宿主机负载会显著影响数据,所以模拟器适合发现数量级问题,正式结论仍应来自目标鸿蒙 PC。

内存要看完整进程组

混合应用会同时存在 ArkTS 主进程、ArkWeb 活跃渲染进程和辅助进程。如果只读取主进程 PSS,CodeMirror 文档、语法树、预览 DOM 和 JavaScript 堆都可能被遗漏。完整进程组 PSS 更接近应用实际占用。

PSS 仍不是唯一指标。测试时还要关注:

  • 打开文档前的稳定基线。
  • 打开 1MiB、10MiB 后的稳定值和峰值。
  • 连续撤销、重做、切换模式后的增量。
  • 关闭文档或重载后内存能否回落。
  • 是否出现 LowMemoryKill、ArkWeb 重启或明显交换。
  • 多次打开文档是否持续增长,提示监听器或旧 EditorView 未释放。

当前 1MiB 完整进程组 PSS 为 324.7MiB,高于 250MiB 目标。正确的工程结论是"加载性能通过,内存仍需优化",而不是用快速加载掩盖内存。优化时应先测各组件增量:关闭预览、语法高亮、行换行或重建历史后分别比较,找到真正的大项。

5MiB 阈值是保护机制而不是魔法数字

达到阈值后切换 minimalSetup,一次性减少多个高成本能力。这种做法稳定,但阈值本身需要真实设备数据校准。字符长度与文件字节不完全对应,结构复杂度也会影响解析和布局,因此未来可以从单阈值演进为基于多信号的策略:

  1. 文件字节和字符长度控制读取与 Bridge 风险。
  2. 行数、最大行长控制换行和滚动风险。
  3. 标题、代码块等结构密度控制语法解析与预览风险。
  4. 当前设备可用内存和 ArkWeb 状态决定是否延迟高成本功能。

策略必须可解释。界面显示 Large file mode,Split 和 Preview 保持位置但不可用,用户仍可以编辑、保存和关闭。不要自动截断文档,也不要只渲染一部分却让保存覆盖完整文件。

输入延迟如何采样

加载快不代表编辑快。大文档输入测试可以在固定位置插入单字符、中文短句和大段粘贴,测量从事务发出到下一次绘制完成的时间。需要分别观察普通输入、语法解析、预览刷新、字数计算、Bridge 消息和恢复快照。

优化优先级通常是先移除与每次按键成全文复杂度的工作:

  • 不在每次变更跨 Bridge 传全文。
  • 大文档不扫描全文计算字数。
  • Preview 只在可见时更新,Source 模式标记为待刷新。
  • 恢复快照节流,并对超大文档使用不同策略。
  • 语法解析和软换行在保护模式关闭。

然后再处理更细的渲染和样式。若一个状态栏动画很顺滑,却仍在每次输入复制 10MiB 字符串,优化方向就是错误的。

撤销重做是典型压力路径

大文档连续撤销可能触发比普通输入更高的峰值,因为历史中保存了变化结构,视图还要重新布局。测试不能只按一次 Ctrl+Z,应构造多次编辑、批量粘贴、连续撤销到基线、再全部重做,并观察内容、脏状态和内存。

修复异常退出时,不能简单关闭撤销来通过性能测试。撤销是桌面编辑器核心能力,合理方案是控制历史规模、减少重复全文快照、避免文档切换历史串联,并对极端场景给出可预测限制。任何限制都要在产品状态或文档中明确,而不是静默丢弃用户历史。

真机验证矩阵

鸿蒙 PC 真机至少应覆盖以下组合:

维度 代表场景
包类型 Release 候选包,记录 SHA-256
窗口 最大化、左右分屏、连续缩放
输入 系统中文 IME、英文、emoji、长句候选
键盘 内置键盘、外接键盘、长按与快捷键
指针 触控板滚动、选择、拖拽、触控点击
文档 1MiB、10MiB、多行、超长行、CRLF
操作 打开、输入、粘贴、撤销重做、保存、重开
稳定性 30 分钟编辑、多次切换、前后台与低内存

每项都要记录设备型号、系统版本、HAP 哈希和样本数。真机测试不需要把每个组合无限展开,但必须覆盖最可能暴露 ArkWeb、IME 和内存差异的代表场景。

性能验收清单

  • 指标同时覆盖正确性、时间、内存和稳定性。
  • 测试语料固定并记录结构、大小和摘要。
  • 加载时间拆分读取、Bridge、EditorState 和首次可编辑帧。
  • 使用 Release 包多次采样,报告 P50/P95 而不是最优值。
  • 内存统计包含完整 ArkWeb 进程组,并观察峰值和回落。
  • 中文 IME 覆盖组合、候选、撤销、重做和保存。
  • 大文件降级保留正文完整性与基本编辑能力。
  • 任何性能优化都重新运行内容无损和保存回归。
  • 模拟器用于开发诊断,正式门槛由目标鸿蒙 PC 真机复核。

性能工程的最终目标不是追求一个漂亮数字,而是确保用户在不同大小文档、输入方式和窗口状态下都能预测应用行为。对混合架构而言,主动减少全文工作、明确降级边界、分层测量和完整进程组数据,比单独优化某个动画更能决定产品上限。

先建立性能预算再谈优化

性能预算把总体目标分配给各阶段,避免每个模块都认为自己的耗时"只有一点"。例如 1MiB 文档首次可编辑目标为一秒,可以为选择器返回后的读取解码、Bridge 传输、EditorState 创建和首帧分别设置观测值;不必机械平均,但任何阶段持续占据大部分预算都应被定位。

内存也需要预算。基线 ArkUI/ArkWeb 进程、空文档、1MiB 文档、语法树、预览 DOM、撤销历史和恢复快照分别测增量。多标签设计时,用单会话增量估算上限,超过目标就先选择冻结或复用,不能等做完十个标签后才发现架构不可承受。

预算不是一次定死。目标设备、ArkWeb 版本和功能变化后可以调整,但调整要记录原因与用户影响,不能为了让当前数字变绿而移动门槛。

用实验矩阵定位真正成本

大文档模式一次关闭多个功能,适合产品保护,却不利于定位。性能分析版本可以逐项对比:basicSetup 与 minimalSetup、Markdown 语法扩展开关、软换行开关、预览开关、字数统计开关、恢复快照开关。每次只改变一个变量,用同一 HAP 配置、语料和操作重复测量。

结果应分别记录加载、输入、内存和正确性。某项扩展可能增加 30ms 加载却明显改善编辑体验,不一定应删除;另一项每次输入都扫描全文,即使首屏影响小也应优先改造。实验目的不是证明某个库"慢",而是找到成本随文档规模增长的函数。

分析完成后,内部开关不应散落在正式产品中。保留清晰的大文件策略,调试实验通过构建配置或测试入口管理。

最长行是容易遗漏的性能变量

编辑器虚拟化通常按可视行工作,但一个数百 KB 的单行文本会让语法解析、选区、换行和水平滚动承受不同压力。日志、压缩 JSON、Base64 或生成代码都可能形成超长行,即使总文件只有 1MiB。

开启软换行时,浏览器需要计算大量视觉行;关闭时,超宽内容又可能影响滚动与绘制。测试应在行首、中间和末尾点击、选择、输入、撤销,并观察滚动条与光标。大文件策略可以在最大行长超过阈值时单独关闭换行,不必等总字符达到 5MiB。

Markdown 代码块中的超长行也应保持原文,不能为了性能自动插入换行字符。视觉换行和文档换行必须严格区分。

粘贴和替换属于峰值操作

单字符输入代表稳定延迟,大段粘贴和全局替换代表瞬时峰值。一次粘贴 5MiB 可能同时触发事务、语法更新、预览、字数、Bridge 和恢复任务,内存短时间出现多份字符串。

处理策略是先让 CodeMirror 完成一个原子事务,再根据新文档规模决定是否进入大文件模式;预览和统计读取最终状态,不对中间版本重复工作。Bridge 只报告最新摘要,恢复快照合并到下一周期。粘贴后应立即可撤销为一个合理步骤。

全局替换同样要限制匹配数量和预览结果,防止构造海量范围对象。替换前后内容正确性、撤销和保存必须进入压力测试。

滚动性能要区分编辑区和预览区

Source、Split 与 Preview 有不同滚动成本。编辑区由 CodeMirror 虚拟化,预览区可能包含完整 HTML DOM;Split 同时存在两者。测试滚动时记录窗口大小、是否软换行、预览 DOM 规模和触控板输入,不能只看鼠标滚轮一次移动。

同步滚动如果未来实现,会在两个区域间增加位置映射和事件回路。需要防止编辑区滚动触发预览、预览回调又反向触发编辑区。可以使用来源标识和动画帧合并,并在大文档关闭。同步精度不应以每次滚动重新解析全文为代价。

掉帧分析要结合主线程任务,确认是 Markdown 渲染、布局、Bridge 还是 ArkUI 外壳更新。肉眼感觉只是发现问题的入口。

长时间稳定性与内存泄漏

短测试可能看不出监听器、旧 EditorView 或预览节点残留。稳定性脚本可以循环打开小/大文档、切换模式、输入撤销、保存和新建,定期采集完整进程组 PSS。若每轮结束后稳定值持续上升,需要检查视图销毁、定时器、Bridge 代理和恢复队列。

测试还应包含前后台、锁屏恢复和 ArkWeb 进程重建。系统回收后能恢复编辑不代表没有泄漏,频繁回收本身可能由内存过高触发。记录系统退出原因和 ArkWeb 进程变化,避免把自动重建误认为正常稳定。

长测不要求用户文档参与,可使用固定无敏感夹具,并在每轮校验最终哈希与内容。

性能可观测性不能记录正文

埋点只需要阶段时间、文档字节/字符区间、行数区间、是否大文件模式、设备和错误码。不要记录文件名、URI、标题和 Markdown。对超长行可以记录长度区间,不记录内容。

本地开发日志提供细粒度时间点,正式版本只保留必要聚合并允许关闭。采样率和保留期限要明确。性能问题复现时,由用户主动选择专用测试文档,不自动上传真实资料。

这样既能观察版本趋势,又不会让性能优化成为隐私风险。

启动、首次可编辑与完全稳定是三个指标

应用窗口出现不代表用户可以输入,CodeMirror 可输入也不代表预览、字数和文件树已经稳定。测量时分别记录应用首帧、编辑器就绪、文档首次可编辑和后台派生任务完成。优化优先保证用户尽快进入可编辑状态,再把预览和索引延后;不能为了一个更短的"启动"数字显示不可操作的空壳。

冷启动包含进程和 ArkWeb 初始化,热启动可能复用系统缓存。两种数据分别报告,测试前明确是否强制停止应用。恢复会话启动还要单列,因为读取沙箱快照和显示决策对话框属于必要工作,不能与空文档混在一起。

系统负载与温度会影响结论

鸿蒙 PC 在电池、充电、温度和后台任务不同条件下可能调整频率与内存。正式性能采样前让设备达到稳定状态,关闭无关重任务,记录电源模式,同时不要为了数字人为关闭产品正常依赖的系统服务。连续压力测试要观察温升后的 P95,而不是只取冷机首轮。

模拟器更受宿主机 CPU、内存和图形负载影响。它适合比较明显回归,报告不把绝对值推广到真机。真机之间也按设备档位建立基线,不用最高配置结果代表全部目标设备。

性能回归阈值需要统计意识

单次从 345ms 变成 365ms 可能只是噪声。回归判断应基于足够样本和分布,设定绝对目标与相对变化双门槛。例如 P95 超过产品目标直接失败;仍在目标内但连续多个版本上升超过一定比例,则进入调查。内存也比较稳定区间和峰值,不用某一秒读数。

自动化环境保存历史趋势和置信范围,设备异常、测试夹具变化或系统升级在图表上标注。确认回归后用实验矩阵定位,不靠重复运行直到出现一次绿色结果。

优化完成必须回到用户任务

关闭所有扩展当然能降低内存,却可能让编辑器失去价值。每项优化要回到打开、输入、滚动、撤销、预览、保存和恢复这些任务,确认速度提高且功能语义不变。性能改动的验收同时包含基准数字和内容哈希、脏状态、格式、故障路径。

当目标冲突时,优先级是用户数据正确、安全、输入可用、可解释降级,最后才是视觉效果。明确优先级可以避免为追求局部帧率引入静默内容损坏。

相关推荐
qq_463408429 小时前
# 基于 HarmonyOS ArkTS 声明式 UI 构建智能音乐播放器:深色主题圆盘轮播、五 Tab 导航隔离、瀑布流收藏与悬浮迷你播放条实现深度解析
ui·华为·harmonyos
<小智>9 小时前
及时做APP开发实战(十九)-数据统计与图表展示
鸿蒙
禅思院9 小时前
Agent记忆管理,是一场工程上的平衡艺术
前端·架构·ai编程
o5278831849 小时前
校园一体化管理系统2.1、基于整洁四层架构 + DDD+CQRS 项目目录解读
后端·架构·c#·asp.net·visual studio
YM52e11 小时前
鸿蒙Flutter Card组件:Material设计风格卡片
android·学习·flutter·华为·harmonyos·鸿蒙
ldsweet19 小时前
《HarmonyOS技术精讲-Basic Services Kit》上传下载进阶:多任务并发与后台长时任务
华为·harmonyos
qizayaoshuap20 小时前
# [特殊字符] 宠物模拟器 — 鸿蒙ArkTS完整技术解析(最终篇)
华为·harmonyos·宠物
不言鹅喻20 小时前
HarmonyOS ArkTS 实战:实现一个校园自动售货机库存与补货记录应用
华为·harmonyos
世人万千丶21 小时前
鸿蒙Flutter TextStyle样式配置
学习·flutter·harmonyos·鸿蒙