BetterAutoSave v0.17.0 — Forge 1.20.1 / NeoForge 1.21.1 异步存档优化
v0.17.0 — 主线程存盘取材成本减半
0.17.0 是一个性能版本。区块存盘在主线程上的取材成本降低约一半,来源是消除一处此前每次快照都会执行、但结果从不被使用的全区块遍历。本版同时修补一处第三方生物群系容器可能被直接引用进异步快照的数据安全隐患
消除快照期的无效方块计数(issue #24)
主线程 capture 此前把每个 section 的两个调色板容器包进一个 LevelChunkSection。该构造器会无条件调用 recalcBlockCounts(),对每个调色板多于一项的 section 遍历全部 4096 格并逐格喂进一个哈希表,一个区块 24 个 section 约合 9.8 万次哈希写入
而它算出的三个方块计数(非空方块数、随机刻方块数、随机刻流体数)只服务于活跃区块的 tick 调度与网络同步,原版序列化 section 时一个都不读——计数全错的副本,落盘字节与正确副本逐字节相同。也就是说这次遍历的结果在存盘链路上从未被使用
本版改为只携带 worker 编码真正需要的两个容器,落盘 NBT 字节不变,不涉及任何 mixin 或原版行为改动
真实生产服前后对比(Forge 1.20.1,137 个 mod,AMD Ryzen 9 9950X3D2,ZGC,60 秒采样,eventCompatMode = PARTIAL):
| 主线程帧 | 改前 | 改后 |
|---|---|---|
快照取材入口 captureWithGeneration |
6692ms(占忙碌时间 19.1%) | 2948ms(8.2%) |
其中 section 取材 copySections |
4096ms | 1024ms |
方块计数重算 recalcBlockCounts |
3152ms | 0(调用已不存在) |
| BetterAutoSave 主线程总占用 | 占忙碌时间 25.8% | 15.2% |
两次采样的存盘区块量以不受本次改动影响的帧交叉核对,改后一轮反而高出约 15%,故上表降幅未被工作量差异夸大
附带收益:每个 section 一个临时哈希表的分配消失后,ZGC 重定位读屏障(forwarding_find)同步下降 32%。调色板容器本身的深拷贝因此也快了 20%,尽管这部分代码未改
需要说明的是,这不会提升一台本就健康的服务器的 TPS——两次采样 TPS 均为 20。它换来的是主线程余量:同样的存盘吞吐占用更少主线程时间,在高负载或大存档下更不容易挤占刻预算
FULL 兼容档不再重复取材
eventCompatMode = FULL 下,快照取材(section 拷贝、光照层克隆、高度图克隆、方块实体 NBT、结构数据拷贝)此前在模式分支之前无条件执行,但该档实际使用的是原版 ChunkSerializer.write 产出的完整 NBT,而 write 内部会自行采集同一批数据。这批先采后废的工作现已跳过
此前为规避 issue #8 而改用 FULL 档的用户,本版起不再承担双倍的主线程取材开销
数据安全
活跃 section 的生物群系容器若不是原版的 PalettedContainer 实现(原版不会出现,仅在第三方 mod 提供只读容器实现时触发),此前会被直接引用进异步快照。只读接口没有拷贝方法,无法脱钩,意味着 worker 编码期间主线程仍可改动同一对象。本版改为在主线程当场编码为 NBT 带走,与活跃容器彻底分离
升级
- 替换 jar 即可,无配置变更,存档格式不变
- 落盘 NBT 与此前逐字节一致,可随时回退到 0.16.3