鸿蒙 PC Markdown 编辑器千文件工作区压力测试

鸿蒙 PC Markdown 编辑器千文件工作区压力测试 鸿蒙 PC Markdown 编辑器千文件工作区压力测试仓库地址https://gitcode.com/VON-/codex_md_oh代码基线工作区搜索功能基线2ca99e9G3-10 千文件设备回归941a1dc当前状态6c9d88f。千文件测试不是制造一千条结果工作区全文搜索在十几个演示文件上运行很快并不能说明它适合真实知识库、项目文档和技术仓库。目录枚举、扩展名过滤、符号链接、UTF-8 解码、正文匹配、结果上限、取消、排序与 ArkUI 列表渲染会随着文件数量放大。G3-10 因此在鸿蒙 PC 模拟器中建立一千文件原生压力语料。最容易写错的压力测试是让一千个文件都包含同一个查询词。OhMarkdown 的工作区结果上限是 500一旦达到上限控制器可以提前停止。测试虽然返回很快却可能只扫描前几百个文件完全没有覆盖“一千文件工作区”。本轮采用尾部唯一命中report-0000.md到report-0998.md只有普通内容排序最后的report-0999.md才包含“唯一压力命中”。控制器必须发现一千个文件、扫描一千个文件、跳过零个才能得到唯一结果。性能数字因此建立在完整遍历上而不是结果上限带来的假快。语料生成必须确定且可清理ohosTest 在应用测试沙箱中动态创建目录和文件不把一千个样本提交进仓库。文件名使用四位补零保证词法排序与数值顺序一致正文短小固定只有最后一个文件出现目标词。awaitfileIo.mkdir(searchWorkspace);for(letindex0;index1000;index1){constsequenceindex.toString().padStart(4,0);constcontentindex999?# 压力文档\n\n唯一压力命中\n:# 文档${sequence}\n\n普通内容\n;awaitwriteRawText(${searchWorkspace}/report-${sequence}.md,content);}确定性语料有三个价值。第一失败时可以直接推导目标路径不依赖随机种子。第二每个文件大小接近性能变化更容易归因于枚举和调度。第三内容完全合成不会把真实用户路径、文件名和正文带进日志或测试报告。测试的beforeEach与afterEach负责递归清理下一轮开始前仍先清理一次避免上次异常中断留下旧文件。压力数据属于测试产物不应污染版本库也不应成为应用首次启动的数据负担。工作区边界先于搜索算法搜索服务不是对任意目录无限递归。当前代码明确限制单目录子项 2000、目录数量 2000、Markdown 文档 5000、单文件可搜索字节 4 MiB、总结果 500、单文件结果 50、快速打开结果 50。支持的后缀包括.md、.markdown、.mdown、.mkd与.txt并排除.git、.hg、.svn、node_modules。constMAX_DIRECTORY_CHILDREN:number2000;constMAX_WORKSPACE_DIRECTORIES:number2000;constMAX_WORKSPACE_DOCUMENTS:number5000;constMAX_SEARCHABLE_DOCUMENT_BYTES:number4*1024*1024;constMAX_WORKSPACE_SEARCH_RESULTS:number500;constMAX_RESULTS_PER_DOCUMENT:number50;constMAX_QUICK_OPEN_RESULTS:number50;constREAD_CHUNK_BYTES:number64*1024;边界不是为了把复杂度藏起来而是让时间、内存和结果列表保持可解释。达到上限时摘要必须标记truncated界面应告诉用户结果被截断。沉默丢弃比明确限制更危险因为用户会误以为没有其他匹配。目录枚举遵守授权范围OhMarkdown 从用户授权的工作区 URI 开始广度遍历不扫描工作区之外的磁盘。条目名先拒绝空名、.、..、斜杠、反斜杠和 NULlstat遇到符号链接直接跳过避免链接把遍历带出授权树或形成循环。constpending:ArrayPendingWorkspaceDirectory[{uri:rootUri,relativePath:}];while(pending.length0documents.lengthMAX_WORKSPACE_DOCUMENTSdirectoryCountMAX_WORKSPACE_DIRECTORIES){constcurrentpending.shift();constlistedNamesawaitfileIo.listFile(getPathFromUri(current.uri),{recursion:false,listNum:MAX_DIRECTORY_CHILDREN1});constvisibleNameslistedNames.slice(0,MAX_DIRECTORY_CHILDREN).filter(isSafeEntryName);// 对每个条目执行 lstat符号链接跳过目录入队Markdown 入表。}每轮枚举请求上限加一用第 2001 个条目判断目录是否被截断再只处理预算内的前 2000 个。最终文档按相对路径排序使相同工作区的搜索顺序可重复也让尾部命中语料真正位于扫描末端。Core File Kit 读取需要防变化全文搜索打开文件时使用只读与NOFOLLOW随后读取stat.size每次最多读取 64 KiB。解码器使用 UTF-8 fatal 模式非法字节不会被静默替换成乱码。读完后必须确认总读取字节等于开始时的大小否则把文件视为搜索期间发生变化。constfileawaitfileIo.open(document.uri,fileIo.OpenMode.READ_ONLY|fileIo.OpenMode.NOFOLLOW);conststatawaitfileIo.stat(file.fd);if(stat.sizeMAX_SEARCHABLE_DOCUMENT_BYTES){thrownewError(The workspace document exceeds the search limit.);}constdecoderutil.TextDecoder.create(utf-8,{fatal:true,ignoreBOM:false});while(totalBytesReadstat.size){constrequestedBytesMath.min(READ_CHUNK_BYTES,stat.size-totalBytesRead);// 分块读取并在每个 await 后检查本轮搜索仍有效。}分块读取的意义不是让整个文件永不进入内存当前实现最终仍会chunks.join()它主要控制单次原生缓冲区并为取消提供检查点。四兆单文件上限则阻止少数巨大文档把工作区搜索拖入大文件渲染同类风险。TaskPool 隔离正文匹配目录枚举和文件读取使用异步 Core File Kit正则或长文本匹配属于 CPU 工作。OhMarkdown 将单篇searchDocumentContent放进低优先级 TaskPool避免在 ArkUI 主线程同步遍历正文。任务返回结果偏移、行列、预览和是否截断原生控制器再合并到本轮摘要。consttasknewtaskpool.Task(searchDocumentContent,document,content,query,options,Math.min(MAX_RESULTS_PER_DOCUMENT,remainingResults));this.activeTasktask;constoutputawaittaskpool.execute(task,taskpool.Priority.LOW)asWorkspaceDocumentSearchOutput;当前实现按文件顺序读取并逐个等待 TaskPool而不是一次创建一千个任务。这种保守模型限制并发内存也保持结果顺序稳定。后续若做并行窗口必须同时约束打开句柄、解码缓冲、任务数量与取消回收不能只追求更小毫秒数。中文整词边界不能照搬 ASCII工作区搜索支持区分大小写、整词和正则。整词判断如果只使用 JavaScript\b中文标题和正文会出现不符合用户预期的边界。服务显式把数字、拉丁字母、下划线、CJK 扩展 A、CJK 统一汉字与兼容汉字视为词字符。这意味着查询“鸿蒙”时紧邻另一个汉字的内容不会被错误当成完整词标点与空白则形成边界。压力语料使用精确中文词组不仅验证文件规模也经过中文匹配路径。真正的语言质量仍应扩展到日文假名、韩文、全角符号和组合字符当前范围不能被宣传成完整 Unicode 分词。正则零长度匹配需要推进用户正则可能产生零长度匹配。如果循环只把lastIndex保持原值TaskPool 会无限执行。当前匹配器检测match[0].length 0后主动将lastIndex加一并在每轮查询中检查任务是否被取消。结果上限也在匹配函数内部执行。单篇达到配额后设置foundMore并停止避免生成大量结果再由 UI 丢弃。性能优化与正确性在这里是一件事只计算产品能够展示和解释的数据。代际取消阻止旧结果覆盖新查询用户在 PC 搜索框中连续输入时上一轮可能还在枚举目录或匹配正文。若旧查询后完成并覆盖面板界面会显示与当前输入不一致的结果。WorkspaceSearchController 为每轮维护 generation新搜索先调用cancelgeneration 加一并取消当前 TaskPool所有异步断点随后通过assertActive检查代际。cancel():void{this.generation1;if(this.activeTask){try{taskpool.cancel(this.activeTask);}catch(_){}this.activeTaskundefined;}}privateassertActive(generation:number):void{if(generation!this.generation){thrownewError(Workspace search canceled.);}}取消不是简单隐藏加载动画。读取循环、目录枚举、任务返回和最终汇总都检查代际旧结果无法在新查询之后提交。基础 ohosTest 还会发起搜索后立即取消并断言观察到 canceled 错误。压力断言覆盖准确与规模千文件用例没有只断言“耗时小于十秒”。它同时验证发现数、扫描数、跳过数、结果数量与目标路径。性能门槛依赖正确性门槛否则返回空数组也会成为最快实现。constsearchSummaryawaitcontroller.search(searchWorkspace,唯一压力命中,{caseSensitive:true,wholeWord:false,regularExpression:false});expect(searchSummary.documentsDiscovered).assertEqual(1000);expect(searchSummary.documentsScanned).assertEqual(1000);expect(searchSummary.documentsSkipped).assertEqual(0);expect(searchSummary.results.length).assertEqual(1);expect(searchSummary.results[0].relativePath).assertEqual(report-0999.md);expect(searchSummary.elapsedMilliseconds10000).assertTrue();当前 MateBook Pro 2in1 模拟器记录全文搜索 419 ms。十秒是跨环境退化保险不是当前性能目标。它给调试构建、模拟器调度和不同开发机留出余量同时仍能拦截从亚秒退化到数量级异常的实现。快速打开必须独立测量快速打开只依赖文档元数据和文件名模糊排序不读取一千份正文。它与全文搜索共享目录收集但产品任务不同前者是“我大致知道文件名”后者是“我只知道正文内容”。把两者混成一个性能数字会让全文搜索看起来虚快或者让快速打开显得不必要地慢。constquickOpenSummaryawaitcontroller.quickOpen(searchWorkspace,report-0999,[]);expect(quickOpenSummary.documentsDiscovered).assertEqual(1000);expect(quickOpenSummary.results.length0).assertTrue();expect(quickOpenSummary.results[0].relativePath).assertEqual(report-0999.md);expect(quickOpenSummary.elapsedMilliseconds5000).assertTrue();最近文件数组故意传空避免会话历史给目标额外加权。当前模拟器记录 50 ms证明一千文件元数据收集与精确文件名排序没有出现秒级停顿。五秒同样是回归上限不是把 50 ms 承诺给全部硬件和目录层级。模糊排序兼顾路径与最近使用文件名完全相等获得最高分前缀匹配次之路径包含再次之最后才做子序列匹配。连续字符、路径段开头、短路径会得到更高分最近打开 URI 只增加权重不改变文件是否存在这一事实来源。排序最后使用相对路径稳定决胜保证同分结果可重复。返回前 50 条可以让面板保持轻量但也意味着空查询或宽泛查询会截断。界面需要保留“结果有限”语义不能让用户误解为工作区只有这些文件。应用内部搜索界面下图来自 OhMarkdown 应用内部工作区搜索面板展示搜索输入、范围与选项在鸿蒙 PC 桌面布局中的承载方式。压力用例调用同一WorkspaceSearchController和 Core File Kit 路径不是 Node.js 中脱离应用的纯字符串跑分。截图证明应用具备真实搜索入口和结果承载面但不能显示 419 ms。时间来自 ohosTest 与 hilog文件规模来自断言界面布局来自应用截图。三类证据各自回答不同问题文章不会用静态画面替代性能日志。快速打开的桌面交互价值鸿蒙 PC 用户使用键盘在大量文档之间跳转时快速打开通常比展开目录树更高效。文件名模糊匹配、最近使用加权、上下键选择、Enter 打开和 Esc 返回编辑器共同组成完整任务。服务层 50 ms 只是其中一部分焦点路由与面板渲染仍需要设备交互验证。产品优势不应只比较算法耗时。标准任务还要计入打开面板的操作数、首次结果稳定时间、误选次数和打开后光标位置。后续与 VS Code、Obsidian、Typora 等竞品比较时必须使用相同语料、冷暖缓存和键盘任务不能拿服务日志与竞品人工感受直接相减。419 毫秒的证据边界419 ms 来自 MateBook Pro 2in1 模拟器、Debug 测试 HAP 和一千个同目录小文件。它包含目录收集、文件读取、TaskPool 匹配和汇总但不包含用户从按快捷键到看见结果列表的所有渲染时长也不是 Release 真机 P95。同目录短文件对元数据访问友好多层目录、网络同步盘、冷缓存、较大正文、复杂正则和大量命中都会改变结果。当前数字可以证明实现没有明显的线性常数灾难也可以作为未来回归基线但不足以宣称比竞品快某个百分比。完整十项 ohosTest 在本轮为10/10Failure 与 Error 都为零。测试 HAP 与 Debug HAP 均记录 SHA-256使日志可绑定产物。即便如此模拟器依然不能替代真实鸿蒙 PC 的文件系统、存储、内存压力和物理键盘体验。压力测试还缺少哪些维度当前样本主要验证文件数量不验证大总字节量。下一组语料应采用多层目录、中文长路径、4 MiB 边界文件、非法 UTF-8、无权限子目录、编辑中变化、同名文件和符号链接。查询矩阵加入大小写、中文整词、复杂正则、零长度正则、大量结果和连续取消。性能维度需要冷/热缓存分组、P50/P95、峰值 PSS、CPU、打开句柄与取消后的资源归零。界面维度要测一百条结果渲染、滚动、键盘选择、准确跳转和窄侧栏。只有服务、系统和交互三层都通过才能把“千文件可用”升级为稳定产品承诺。从一千文件走向增量索引当前本地直接扫描符合“无数据库、低复杂度、内容实时”的 G3 边界。一千个小文件 419 ms 说明现阶段不需要提前引入常驻索引。索引会带来文件监听、失效、迁移、磁盘占用、隐私、加密和损坏恢复等长期成本。当真实用户工作区达到数万文件或正文总量显著上升才应以数据触发架构升级。可选方向包括按文件指纹缓存、倒排索引、增量监听和分片持久化但必须保留无索引回退与重建。性能优化不能让搜索结果比磁盘事实更旧也不能把本地正文上传到云端。隐私与日志原则压力语料使用固定中文和序号日志只记录发现数、扫描数、跳过数、耗时与合成相对路径。生产日志不应记录用户查询词、正文预览、完整路径或文件名。搜索结果只存在当前应用会话工作区授权由系统文件能力控制。后续匿名质量数据也只需要数量区间、耗时桶、是否取消和错误类型。为了分析性能而上传“最慢文件路径”会泄露项目结构为了分析搜索准确性而上传查询词可能泄露客户、密码或内部代号。本地优先产品的优势需要在测量阶段继续成立。可复现测试清单复现时应固定代码提交、测试 HAP 哈希、HarmonyOS 镜像、设备配置和语料生成代码。先清理测试目录安装最新 ohosTest HAP执行指定套件保存 hilog结束后确认目录删除。若只重跑单项还要再执行全量10/10防止测试隔离依赖被掩盖。结果报告至少包含发现、扫描、跳过、命中、目标路径、全文搜索耗时和快速打开耗时。任何一个正确性断言失败性能数字作废若缓存状态不明标记为未分类不与历史基线直接比较。这样的记录才能让不同开发机和后续真机数据进入同一张表。对鸿蒙 PC 编辑器优势的贡献文件树解决“浏览”快速打开解决“知道名字”全文搜索解决“只记得内容”。三种导航方式并存才适合桌面上的重复工作。千文件压力把工作区搜索从功能演示推进到规模证据也让结果上限、取消、安全边界和 UTF-8 失败路径具备明确预算。OhMarkdown 当前优势不是宣称拥有最复杂的索引而是在无需联网和常驻数据库的条件下一千文件仍能准确完整扫描并在用户继续输入时取消旧任务。架构保持简单证据保持可复现后续只有在真实规模推动时才增加索引复杂度。结论鸿蒙 PC Markdown 编辑器的千文件测试必须同时验证规模、准确、取消、资源边界和任务语义。OhMarkdown 使用尾部唯一命中避免结果上限造成假快在模拟器中确认发现一千、扫描一千、跳过零、唯一命中report-0999.md全文搜索 419 ms快速打开 50 ms。这些结果绑定941a1dc代码与原生 ohosTest足以建立 G3-10 内部压力基线但不替代 Release 真机、多层大语料、冷缓存、UI P95 和统一竞品测试。可靠的性能文章不是只给出一个漂亮数字而是说明这个数字经过了哪些路径、排除了哪些捷径以及下一步还必须验证什么。