From 164f4749460a304c1aaad4900ef3facc29859eb0 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 10:36:39 +0800 Subject: perf(kernel): P27 第一步 — Rumor 长跑基准与修复前量化 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Kernel 层 Rumor 长跑基准(RumorBench.fs + headless --rumor-bench [days npcs chats_per_day]),把「聊天→生成/追溯谣言」路径从 Sim.step 的 NPC 快照分配解耦,隔离测量谣言工作集随世界年龄的分配/耗时;同 Config 的 retained_id_checksum 证明确定性。 复核 M3-1 前提:World.Rumors 已被 M6a(d94ec2d/cc850bf)按 3 天保留窗口收敛, 单日成本平稳(1/5/10 天 ≈18.7/18.1/19.8s、≈19GB/天),超线性项消失。 剩余 Rumor 成本 = 每次聊天的两条 O(N) 扫描 latestRumorFor/rumorIsDuplicate, 且 [] NpcId/RumorId 用 = 比较会逐条装箱(Sim.chat 单独测得 N=16000 时 393,736 B/次、随 N 线性)。 修复前基准(30 NPC,--rumor-bench D 30 2000): 3d chat_calls=6000 retained=6000 alloc=930,716,456 ms=1626.7 30d chat_calls=60000 retained=8000 alloc=13,419,045,864 ms=12182.5 60d chat_calls=120000 retained=8000 alloc=27,297,668,608 ms=23769.7 (60d 工作集稳定 8000,不随年龄增长。) docs/维护说明.md 新增 P27 小节记录上述现状与量级。不改任何模拟语义。 --- ...273\264\346\212\244\350\257\264\346\230\216.md" | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'docs') diff --git "a/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" "b/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" index cc3a698..7be174a 100644 --- "a/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" +++ "b/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" @@ -459,6 +459,31 @@ dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll 各出一格,相邻民居极近时挑檐可能视觉叠压,可在布点时加 1 格间隔;③ 巡游 40s 档的 `visible>=8` 占比仅 13%(P25 150s 档为 74%),属取证时长差异,非可见性回归。 +## P27:Rumor 长局性能修复(工作集扫描去装箱 + 裁剪判定) + +- **现状复核(更正 M3-1 前提)**:M3-1 记录的「`World.Rumors` 不裁剪、逐次聊天全表扫描」已由 M6a + (`d94ec2d` 工作集容量上界+按天淘汰、`cc850bf` 窗口化早停扫描)修复:`RumorEvent.DayIndex` 派生字段 + + `trimRumors` 把工作集按「最新在前 + 3 天保留窗口」收敛。实测单日成本已平稳(本机 `--cost-probe`: + 1 天 18.7s/18.9GB、5 天 90.5s/95.7GB、10 天 198.2s/191.9GB,≈19GB/天常数),M3 记录的 20 天 + ≈45 s/天的超线性项已消失。故 P27 不采用「砍 depth/strength 传播」这类会改变模拟语义的方案。 +- **剩余 Rumor 成本定位**:工作集有界后每次聊天仍有两条 O(N) 扫描 `latestRumorFor` / `rumorIsDuplicate`; + 且此前用 `=` 比较 `[]` 的 `NpcId`/`RumorId`,会走泛型结构相等,对每一条被扫描到的谣言 + 装箱(实测 ≈24 B/条、单次聊天随 N 线性增长——`Sim.chat` 单独测得 N=16000 时 393,736 B/次); + 这是 Rumor 逐条分配的主因,而非列表 cons 本身。 +- **Kernel 长跑基准**:新增 `src/LivingVillage.Kernel/RumorBench.fs` 与 headless + `--rumor-bench [days npcs chats_per_day]`,把「聊天→生成/追溯谣言」路径从 `Sim.step` 的 NPC 快照 + 分配解耦,按模拟日推进 tick、固定节奏注入聊天,测工作集分配与耗时;同 `Config` 的 + `retained_id_checksum` 可证明确定性。 + 修复前(30 NPC,`--rumor-bench D 30 2000`): + + | days | chat_calls | rumors_retained | allocated_bytes | elapsed_ms | + |---|---|---|---|---| + | 3 | 6000 | 6000 | 930,716,456 | 1626.7 | + | 30 | 60000 | 8000 | 13,419,045,864 | 12182.5 | + | 60 | 120000 | 8000 | 27,297,668,608 | 23769.7 | + + 60 天工作集稳定在 8000(M6a 的 3 天保留窗口 + 2000/天),不再随年龄增长。 + ## 已验证命令 ```bash -- cgit v1.2.3 From 593b3a69294f2c93df1e14d4be7d2f4cd9f9ac14 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 10:48:11 +0800 Subject: docs: P27 小节补全修复后指标与等价性/门槛 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补 P27 修复策略(扫描去装箱 + 裁剪判定 O(1))、修复前后基准对照 (60 天 alloc -64%、ms -28%)、retained_id_checksum 前后一致、 5 天双跑 WorldSave 逐字节相同、digest/PASS 与 Kernel 92/Desktop 143 门槛; 并记录全模拟 --cost-probe 基本持平的原因(逐 tick NPC 快照分配主导, 谣言子系统占比小,留给后续单)。已验证命令补 --rumor-bench。 --- ...273\264\346\212\244\350\257\264\346\230\216.md" | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'docs') diff --git "a/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" "b/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" index 7be174a..dbbf2ac 100644 --- "a/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" +++ "b/docs/\347\273\264\346\212\244\350\257\264\346\230\216.md" @@ -483,6 +483,32 @@ dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll | 60 | 120000 | 8000 | 27,297,668,608 | 23769.7 | 60 天工作集稳定在 8000(M6a 的 3 天保留窗口 + 2000/天),不再随年龄增长。 +- **修复策略(两条,均不改保留集合与查询结果)**: + 1. **扫描去装箱**:`latestRumorFor` / `rumorIsDuplicate` 原来用 `=` 比较 `[]` 的 + `NpcId`/`RumorId`(含 `RumorId option`),走泛型结构相等、逐条装箱。改为先解构底层 + `int`/`int64` 再比较(`Parent` 用 `int64 option` 比较);语义完全等价,扫描不再逐条分配。 + 2. **裁剪判定 O(1)**:`World` 新增派生镜像 `RumorCount`/`RumorOldestDay`(不参与存档,读档由 + `Sim.rumorWorkingSetStats` 从列表重建)。`appendRumor` 用「`count+1 > capacity` 或 + `min(oldest, day) < cutoff`」O(1) 判定;旧 `consRumor` 每次追加都 `rumorsNeedTrim` 全表扫一遍。 + 判定条件与 `rumorsNeedTrim` 逐字等价,仅真需裁剪时才走 `trimRumors`,保留集合逐条相同。 +- **修复后基准(同机、同 `--rumor-bench D 30 2000`)**: + + | days | 修复前 alloc / ms | 修复后 alloc / ms | alloc 降幅 | + |---|---|---|---| + | 3 | 930,716,456 / 1626.7 | 482,716,192 / 1511.3 | −48% | + | 30 | 13,419,045,864 / 12182.5 | 4,884,067,640 / 8879.6 | −64% | + | 60 | 27,297,668,608 / 23769.7 | 9,774,660,360 / 17078.2 | −64% | + + 60 天分配 −64%、耗时 −28%;`retained_id_checksum=927964000`(3/30/60 天分别为 + 17997000 / 447996000 / 927964000)与修复前逐位一致,证明保留集合未变。 +- **等价性与门槛**:5 天 2000 聊天/天连跑两遍 `WorldSave.save` 逐字节相同; + `RumorCount`/`RumorOldestDay` 与对列表重算一致(count=8000、oldest_day=1)。 + `final_digest=953775FAEB2FDDE97289491AA260BD8D390C571E48A7A13AD2CB6FB7124F7F6C` + 三次一致、`performance_determinism=PASS`;Kernel 92 / Desktop 143 全绿。 +- **全模拟口径(`--cost-probe`)**:1 天 18.7s/18.8GB、5 天 90.4s/94.6GB,与修复前基本持平—— + 全模拟分配由逐 tick 的 `Npc[30]` 快照(`Sim.step` 的 `Array.copy`,≈120 B/NPC/tick)主导, + 谣言子系统占比很小;P27 的收益集中在 Rumor 工作集路径,符合「隔离测量」的基准口径。 + 逐 tick NPC 快照分配属另一个课题(会触及不可变快照不变量),留给后续单。 ## 已验证命令 @@ -502,6 +528,10 @@ LV_BATCH_WORKERS=4 dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVi # 长局成本阶梯(只读,真实测量) dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll --cost-probe 1,5,10 +# P27 Rumor 长跑基准(隔离谣言子系统;默认 30 NPC×60 天×2000 聊天/天) +dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll --rumor-bench +dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll --rumor-bench 3 30 2000 + # 素材再生成(确定性,输出 byte-identical) python3 scripts/make-jiangnan-art.py && python3 scripts/make-cc0-art.py -- cgit v1.2.3