服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
256 lines
14 KiB
Markdown
256 lines
14 KiB
Markdown
# 索引性能验证
|
||
|
||
开发版和基准必须使用相同的 Go 编译参数。开发构建使用
|
||
`-gcflags="pvfine/...=-l"`,仅关闭项目代码的内联;SQLite 和标准库保持优化。
|
||
原来的 `-gcflags=all=-l` 会同时关闭纯 Go SQLite 及其依赖的内联,不能与
|
||
默认 `go test` 的耗时直接比较。
|
||
|
||
## 真实归档强制重建
|
||
|
||
在仓库根目录运行,`PVF_TESTFILE` 可替换为待测文件:
|
||
|
||
```sh
|
||
PVF_MEMORY_TEST=1 PVF_REBUILD_TEST=1 PVF_TESTFILE="$PWD/testdata/90CN.pvf" \
|
||
go test -gcflags='pvfine/...=-l' ./services \
|
||
-run '^TestLargeArchiveMemory$' -count=1 -v
|
||
```
|
||
|
||
若要复现旧开发版的性能问题,仅将编译参数替换为 `-gcflags=all=-l`。
|
||
两种测试应顺序运行,避免相互竞争 CPU 和磁盘。测试末尾的 GC 用于单独测量
|
||
存活堆;`forced_rebuild` 不包含这次 GC。
|
||
|
||
2026-09-21 在同机、同一份 1,057,873 文件归档和同一份索引实现上的各一次结果:
|
||
|
||
| 指标 | 原开发参数 `all=-l` | 修正参数 `pvfine/...=-l` |
|
||
| --- | ---: | ---: |
|
||
| 强制重建总耗时 | 54.34 s | 10.41 s |
|
||
| 语义加载 | 11.525 s | 3.790 s |
|
||
| 文件补齐 | 19.424 s | 2.499 s |
|
||
| 创建搜索索引 | 18.907 s | 2.211 s |
|
||
| GC 后存活堆 | 437.5 MiB | 437.5 MiB |
|
||
| 测试进程累计堆分配 | 32,906.6 MiB | 4,521.7 MiB |
|
||
|
||
这组结果用于定位编译参数造成的性能差异,不代表三次冷/热缓存验收、
|
||
桌面 WebView 物理内存测量或所有平台的性能保证。累计分配不是峰值或物理内存。
|
||
|
||
修改 Taskfile 后需重新构建并重启开发应用;仅刷新前端不会替换 Go 进程。
|
||
可用 `go version -m bin/pvfine` 检查二进制中的 `-gcflags`。
|
||
强制重建仍重新解析语义并构建候选数据库,不通过缓存命中跳过重建。
|
||
|
||
## 110US 全量重建优化(2026-10-13)
|
||
|
||
归档包含 4,311,296 个文件。用户提供的开发应用日志中,强制重建耗时
|
||
202.29 秒。优化后使用同一份本地归档,按开发配置关闭项目代码内联,
|
||
顺序运行三次;每次创建新的 Core、打开已有完整文件索引缓存,再执行强制
|
||
重建。重建仍读取脚本元数据,不通过语义缓存跳过重建;操作系统缓存未清空。
|
||
|
||
```sh
|
||
PVF_MEMORY_TEST=1 PVF_REBUILD_TEST=1 PVF_TESTFILE="$PWD/testdata/110US.pvf" \
|
||
go test -gcflags='pvfine/...=-l' ./services \
|
||
-run '^TestLargeArchiveMemory$' -count=3 -timeout=15m -v
|
||
```
|
||
|
||
三次强制重建:**72.09 / 72.11 / 72.47 秒**,中位数 **72.11 秒**。
|
||
|
||
| 阶段 | 用户原日志 | 优化后三次中位数 |
|
||
| --- | ---: | ---: |
|
||
| 候选库准备(复制文件投影) | 10.05 s | 16.36 s |
|
||
| 清理旧语义数据 | 28.50 s | 0.00 s |
|
||
| 语义加载及关联写入 | 115.99 s | 31.51 s |
|
||
| 建立文件关联索引 | 2.10 s | 1.24 s |
|
||
| 普通文件补齐 | 21.85 s | 0.00 s |
|
||
| 建立搜索及依赖索引 | 23.49 s | 22.36 s |
|
||
|
||
旧路径先复制整库,再删除旧语义及普通文件重复记录;新候选库只复制
|
||
`meta/files/dirs` 并创建空语义表。未登记文件直接从 `files` 查询,以两个有序
|
||
数据流分页,省去 3,674,085 条重复记录。语义依赖按表编号缓存路径、按文件
|
||
去重,每 128 条使用预编译 SQL 批量写入;全量加载后统一建立依赖索引。
|
||
增量刷新复用相同依赖写入器,但保留已有索引。
|
||
|
||
三次均得到 638,532 条登记记录、637,211 条 visuals、979 个跳过条目,
|
||
与用户日志的对应计数一致,元数据解析错误均为零。新增回归覆盖虚拟普通
|
||
文件搜索、跨数据流分页、多列表登记、取消/恢复登记、WAL 中已提交文件投影、
|
||
批量尾部依赖,以及关闭期间后台重试;全量 Go 测试和相关竞态测试通过。
|
||
发布仍校验归档代际与修订,完整候选事务提交后才替换活动索引。
|
||
|
||
相较用户日志,中位耗时约减少 64%。原数据为应用日志,新数据为服务层测试,
|
||
没有对原二进制做相同缓存条件下的三次重跑,因此这是日志对照,不是严格
|
||
控制变量的提速倍数。重建计时不含归档打开、最后的 GC 和 WebView。
|
||
新 `create-indexes` 阶段也包含依赖索引建立,阶段内部工作量与旧路径不同。
|
||
剩余主要成本是文件投影复制、字符串依赖写入和索引排序。
|
||
|
||
文件索引 schema 升级为 5,并使用新的缓存身份;旧缓存需要首次建库,之后
|
||
可复用。重新构建并重启 Go 应用进程才能启用实现;仅刷新前端不会生效。
|
||
|
||
## SQLite 语义增量刷新
|
||
|
||
2026-10-13,新增 `BenchmarkSQLiteSemanticRefresh`,在合成归档中创建
|
||
100,000 个装备脚本及一个登记列表。初次建库不计时;每次改变一个脚本名称,
|
||
对比完整语义构建与增量刷新。两种模式使用同一个测试可执行文件和默认 Go
|
||
编译参数,各运行三次,每次测量一次刷新。
|
||
|
||
```sh
|
||
go test ./services -run '^$' -bench '^BenchmarkSQLiteSemanticRefresh$' \
|
||
-benchtime=1x -count=3
|
||
```
|
||
|
||
| 模式 | 三次耗时 | 中位数 |
|
||
| --- | --- | --- |
|
||
| 完整语义构建 | 1003.4 / 984.4 / 982.6 ms | 984.4 ms |
|
||
| 增量刷新 | 184.0 / 179.7 / 179.1 ms | 179.7 ms |
|
||
|
||
这组数据约为 5.5 倍差异。测量包含增量暂存克隆、登记读取和 SQL 发布,
|
||
不包含初次建库、实际编辑动作、前端桥接和文件投影更新。合成脚本的未修改
|
||
内容为空,不能视为真实百万文件归档或完整用户操作的耗时保证。
|
||
|
||
普通修改保留未受影响的语义行和数据库索引;首次建库、强制重建和修复仍
|
||
允许全量构建。删除仍需同步文件编号映射,并不是常数时间操作。
|
||
|
||
## 高级字符串搜索(SQLite)
|
||
|
||
高级字符串搜索统一使用磁盘反向索引,小归档也不再构建服务层的全量
|
||
`StringPoolIndex`。字符串池值只存一份,引用表保存真实池偏移、文件编号、
|
||
出现次数、token 类型和文件表字段。普通子串仍使用 Go 的 Unicode 小写规则,
|
||
正则仍使用 Go regexp。查询只扫描池记录,不重复解压归档;按路径、文件编号
|
||
排序的结果文件编号另存会话库,详情按当前页关联读取。
|
||
|
||
- 首次使用才建库。构建分批提交,解码时只保留当前块和当前文件的引用。
|
||
- 干净归档以加载内容的 SHA-256 和索引版本作为缓存键,缓存命中执行完整性检查。
|
||
索引放在用户缓存目录 `pvfine/advanced-search`,历史缓存预算 2 GiB。
|
||
- v4 缓存使用不可变文件及会话硬链接,历史缓存淘汰不会删除活动会话使用的索引。
|
||
不支持硬链接的文件系统仍可使用会话索引,但不能跨打开复用。
|
||
- 未保存归档只使用会话库,不发布干净缓存。编辑、结构变化和版本切换使查询
|
||
结果及游标失效,已完成的引用底库保留。下一次搜索仅解析新增/修改文件,
|
||
通过私有覆盖表和编号映射屏蔽旧引用;引用分片不重写。池字符串只追加新增
|
||
部分,已有占位符重新解析。池前缀改变时才回退到完整构建。
|
||
- 首次更新只复制元数据库,不复制引用分片;保存后追平的覆盖结果可发布
|
||
新缓存。可变元数据库使用 backup 快照,引用分片继续使用不可变硬链接。
|
||
- 查询会话最多 8 个,结果库合计预算 256 MiB,淘汰会删除实际文件;单次查询
|
||
超过可用预算会明确报错。游标包含查询身份,旧游标不能用于新查询或新归档。
|
||
- 取消按钮、归档关闭和编辑会取消旧任务并异步释放连接及会话目录;干净缓存保留。
|
||
- 这次迁移针对字符串模式。二进制搜索沿用现有实现。
|
||
|
||
复现:
|
||
|
||
```sh
|
||
PVF_MEMORY_TEST=1 PVF_ADVANCED_TEST=1 PVF_TESTFILE="$PWD/testdata/90CN.pvf" \
|
||
go test -gcflags='pvfine/...=-l' ./services \
|
||
-run '^TestLargeArchiveMemory$' -count=1 -v
|
||
```
|
||
|
||
2026-09-21,同机顺序运行、每场景一次,查询 `npc`,每页 200 个文件:
|
||
|
||
| 指标 | 测量值 |
|
||
| --- | ---: |
|
||
| 池字符串数 | 1,643,770 |
|
||
| 文件与池偏移引用数 | 26,804,667 |
|
||
| 首次建库 | 76.7 s |
|
||
| 反向索引文件 | 1,162,321,920 字节(约 1.08 GiB) |
|
||
| 已有缓存的完整性检查及打开 | 6.9 s |
|
||
| 已有缓存的首次查询(包含检查) | 7.6 s |
|
||
| 首次建库之后,普通查询生成结果 | 约 0.8 s |
|
||
| Go 正则查询生成结果 | 约 1.1 s |
|
||
| 首次建库后连续 10 页,端到端耗时范围 | 8–71 ms |
|
||
| 高级搜索后 GC 存活堆 | 约 364 MiB |
|
||
|
||
这些是服务层真实归档测量,未包含 WebView 物理内存,不是跨机器性能承诺。
|
||
终端日志使用 `[pvfine:advanced]`,仅记录条数、容量和耗时,不记录池内容或查询文本。
|
||
|
||
## 元数据提取与按需图标合批(2026-10-14)
|
||
|
||
`ScriptMetadata` 保留配对/非配对 section、嵌套层级和文档顺序语义,
|
||
只收集名称、图标、场景图、稀有度和角色名称所需的值。名称只保存首个有效
|
||
字符串,图标只保存首个有效相邻路径/帧编号对,其余目标最多保存首个 token;
|
||
无关 section 的值不解码。每次提取按偏移复用标签解析结果,不建立跨编辑的
|
||
标签缓存,也不修改字符串池。
|
||
|
||
Apple M4 Max、默认 Go 编译参数下,同一合成脚本分别在修改前后运行三次:
|
||
|
||
```sh
|
||
go test ./internal/pvf -run '^$' -bench '^BenchmarkScriptMetadataLargeScript$' \
|
||
-benchmem -count=3
|
||
```
|
||
|
||
| 指标 | 修改前 | 修改后 |
|
||
| --- | ---: | ---: |
|
||
| 单次耗时(三次中位数) | 220,775 ns | 42,124 ns |
|
||
| 单次分配字节(三次中位数) | 769,279 B | 28,208 B |
|
||
| 单次分配次数 | 814 | 23 |
|
||
|
||
样本含 100 个无关 section,每个含 40 个字符串及 40 个整数,并带名称、
|
||
图标、稀有度字段。建样本不计时,测量为重复读取内存脚本的热路径,不包含
|
||
真实归档打开、分块解压、本地化首次加载、SQLite 写入或 WebView。因此约
|
||
5.2 倍的提取差异不能解释为真实归档整体重建的提速倍数。
|
||
|
||
图标仍按实际请求加载。前端等待 8 ms 合并请求,每批最多 128 帧、最多
|
||
两个桥接批次同时执行。后端按 NPK 分组、按 IMG 排序,同批每个 NPK 只打开
|
||
一次,每个 IMG 只解析一次帧表;整个服务最多同时处理四个 NPK 解码任务。
|
||
每个任务只保留当前 IMG 帧表。重复路径别名按索引中的实际成员路径去重,
|
||
错误按帧返回,资源代际或快照变化后丢弃旧结果。旧 `GetImage` 入口复用相同
|
||
并发限制和缓存,绑定由 Wails 重新生成。
|
||
|
||
合成回归覆盖两个 NPK、三个 IMG、多帧、重复别名及无效引用的混合请求,
|
||
验证首次请求恰好打开两个 NPK、解析三个帧表,已缓存帧的重复请求不执行 IO。
|
||
另覆盖损坏成员隔离、过期代际、读取中切换代际、跨请求并发上限,以及前端
|
||
合批、排队、缓存、重试和迟到响应失效。这些是行为与读取次数验证,尚未测量
|
||
真实 NPK 目录下的滚动延迟或物理内存变化。
|
||
|
||
## 高级字符串搜索:worker 解析工作区复用(2026-10-14)
|
||
|
||
引用建库的每个 worker 独占一个 `StringReferenceWorkspace`,复用偏移去重
|
||
map、引用数组及 token 类型、文件字段小切片。返回结果为借用切片,对应
|
||
SQLite 分片 writer 必须同步消费;写入完成后 channel 只传文件信息和引用
|
||
数量,避免下一文件覆盖尚未读取的结果。原有 `StringReferenceInput.References`
|
||
继续返回独立结果,扫描器及增量调用的结果生命周期不变。
|
||
|
||
工作区超过 32,768 条引用容量时,在消费后立即释放;这个限制只控制后续保留
|
||
的临时容量,不截断大文件引用。正常工作区清理逻辑状态后复用,错误、取消
|
||
及 worker 退出仍遵循原来的任务取消协议,不复用可能被其他输入持有的解压块。
|
||
|
||
在 Apple M4 Max、36 GiB 内存、macOS 26.6、Go 1.25.8 darwin/arm64 上,
|
||
使用 4,311,296 文件的 110US 归档及开发编译参数顺序测试。实施前与复用原型
|
||
各运行三轮;正式实现再复测一轮,均绕过索引缓存,未清空操作系统缓存。
|
||
|
||
| 指标 | 实施前(三轮中位数) | 正式实现(一次复测) |
|
||
| --- | ---: | ---: |
|
||
| 完整高级字符串索引建库 | 103.68 s | 94.14 s |
|
||
| 累计分配 | 78.75 GiB | 57.55 GiB |
|
||
| 分配次数 | 1,025,812,242 | 756,516,298 |
|
||
| 文件与字符串偏移引用数 | 122,196,685 | 122,196,685 |
|
||
|
||
累计分配减少约 21.2 GiB(26.9%),分配次数减少约 26.3%。实施前耗时为
|
||
110.61 / 103.68 / 96.30 秒,复用原型为 95.26 / 96.79 / 98.32 秒;原型测试
|
||
之后补跑实施前代码得到 95.45 秒。因此耗时差异仍可能来自缓存及环境波动,
|
||
本次优化不承诺稳定的完整建库提速。累计分配不是峰值、存活堆或 RSS,不能
|
||
据此声称应用物理内存下降 26.9%。
|
||
|
||
此前独立解压及引用扫描探针在同一真实归档上交替运行现有/原型各三轮:
|
||
中位耗时 16.07 / 14.18 秒,累计分配 40.97 / 19.77 GiB,GC 次数 24 / 11。
|
||
该探针包含解压、解析、排序及计数,不含 SQLite、归档打开或文件路径投影,
|
||
不能把扫描提速直接视为完整建库提速。
|
||
|
||
真实归档逐文件校验全部引用的偏移、出现次数、token 类型顺序和字段标记,
|
||
与独立结果解析一致。新增回归覆盖同偏移多字段、类型顺序、尾部不完整 token、
|
||
跨文件复用、大容量回收、解析中取消、同步 writer 消费及阻塞 writer 取消。
|
||
全量 Go 测试、相关竞态检查及前端构建通过。
|
||
|
||
真实归档一致性验证(未设置 `PVF_REFERENCE_TEST=1` 时跳过):
|
||
|
||
```sh
|
||
PVF_REFERENCE_TEST=1 PVF_TESTFILE="$PWD/testdata/110US.pvf" \
|
||
go test -gcflags='pvfine/...=-l' ./internal/pvf \
|
||
-run '^TestStringReferenceWorkspaceRealArchive$' -count=1 -v -timeout=5m
|
||
```
|
||
|
||
完整建库复测:
|
||
|
||
```sh
|
||
PVF_TESTFILE="$PWD/testdata/110US.pvf" \
|
||
go test -gcflags='pvfine/...=-l' ./services -run '^$' \
|
||
-bench '^BenchmarkAdvancedIndexRealArchive$' -benchtime=1x -count=1 \
|
||
-benchmem -timeout=15m
|
||
```
|
||
|
||
该基准只在内存中追加一个未引用的池字符串来阻止缓存复用,不保存归档。
|
||
建库计时不含归档打开、缓存恢复校验和关闭清理;缓存校验时间单独报告。
|