summaryrefslogtreecommitdiff
path: root/artifacts/perf-multiworld/20260928T195839Z/README.md
blob: f5dc67687580e4b931877b408b4be67b1f4823b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# P59 多世界并存压测结论(Sim 多世界 × 天数的内存/GC 增长曲线)

- 日期:2026-09-28
- 代码 commit:`2c559a7`(本 slice 起点;压测期间未改 Kernel/Sim)
- 运行方式:独立 `systemd --user` transient unit `living-village-multiworld.service`
  (wrapper `scripts/profile-multiworld.sh`,`StandardOutput=append:` + `exec 2>&1` 汇一个文件)
- 证据:本目录 `acceptance.log`(主运行 N=10)、`control-n1.log`(控制组 N=1)、`state.json`
- 只读结论,**未修改任何 Kernel/Sim 语义**;`--performance-baseline` ×3 digest 仍为
  `953775FAEB2F…`,Kernel 119/119。

## 口径

`dotnet LivingVillage.Headless.dll --profile-multiworld <worlds> <days> <npcs> <sampleEveryDays> <seedBase>`

- N 个 `Sim.initialWorldN(seedBase+i, 30)` 世界**同时存活**;
- 按采样轮推进:每轮用 `Parallel.For` 让全部世界各前进 `sampleEveryDays` 天,随后采样一次;
- 采样字段:`worlds`(并存世界数)、`events`、`rumors`、`annals`、`memory_entries`
  (全部世界的聚合)、`gc_live_mb`(`GC.GetTotalMemory(true)`)、`working_set_rss_mb`
  (`Process.WorkingSet64`)、`elapsed_s`;每行 flush;
- ground truth 为 **gc_live 与 RSS**;条目计数只作解释用。

### 标定探针(先跑)

`--profile-multiworld 1 2`(1 世界 × 2 天)= **34.2s**,与 `docs/overnight-status.md`
中 Hermes 单 worker 亲测 34.6s 一致。据此定 **N=10 × 20 天**(10 世界并行一轮约 100s)。

## 主运行 N=10 × 20 天(12 天口径内的采样,`acceptance.log`)

| day | rumors | annals | memory_entries | gc_live_mb | rss_mb | elapsed_s |
|----:|------:|------:|--------------:|----------:|------:|---------:|
| 0 | 0 | 0 | 0 | 0.12 | 39.05 | 0.0 |
| 2 | 38536 | 1280 | 19200 | 7.28 | 74.61 | 101.0 |
| 4 | 76353 | 1280 | 19200 | 12.19 | 91.55 | 206.1 |
| 6 | 75488 | 1280 | 19200 | 12.09 | 102.58 | 309.6 |
| 8 | 75737 | 1280 | 19200 | 12.13 | 123.10 | 412.4 |
| 10 | 75667 | 1280 | 19200 | 12.11 | 106.71 | 514.9 |
| 12 | 75908 | 1280 | 19200 | 12.15 | 112.00 | 616.9 |
| 14 | 76017 | 1280 | 19200 | 12.16 | 110.58 | 719.2 |
| 16 | 75334 | 1280 | 19200 | 12.06 | 111.41 | 820.4 |
| 18 | 75907 | 1280 | 19200 | 12.15 | 105.52 | 922.5 |
| 20 | 76687 | 1280 | 19200 | 12.25 | 103.96 | 1024.3 |

wall = **1024.4s(≈17.1 min,< 40 min 预算)**,exit=0。

## 控制组 N=1 × 20 天(`control-n1.log`)

| day | rumors | annals | memory_entries | gc_live_mb | rss_mb | elapsed_s |
|----:|------:|------:|--------------:|----------:|------:|---------:|
| 2 | 3921 | 128 | 1920 | 0.85 | 57.19 | 34.0 |
| 4 | 7843 | 128 | 1920 | 1.37 | 61.71 | 68.1 |
| 20 | 7932 | 128 | 1920 | 1.39 | 67.94 | 339.0 |

wall = 339.1s。世界 0 的 `world_digests=0:2364CB74F5FCA345…` 与 N=10 主运行的
世界 0 **逐字节一致** → 并存 + 并行推进不改变单世界语义。

## 结论

### 1) 增长是否随世界数超线性?——否,近似线性(内存实为略次线性)

以 N=1 与 N=10 同日(D=20)对比:

| 量 | N=1 | N=10 | 倍率 |
|---|---:|---:|---:|
| rumors | 7932 | 76687 | 9.67× |
| annals | 128 | 1280 | 10.00× |
| memory_entries | 1920 | 19200 | 10.00× |
| gc_live_mb | 1.39 | 12.25 | 8.81×(每世界 1.23MB) |
| wall_s | 339 | 1024 | 3.02×(10× 工作量,16 核并行) |

条目计数严格 ~N 倍;GC live set **略低于** N 倍(运行时/JIT/GC 堆共享的固定开销被摊薄)。
**未观测到任何超线性增长**。

### 2) RSS 是否有界?——有界,无单调泄漏

- N=10:39MB → 峰值 123MB(day 8)→ 回落并稳定在 **104–112MB**,day 10 后不再上升。
- N=1:39MB → day 4 后稳定在 **64–68MB**。
- 时间维度上,10 世界每 2 天一轮墙钟恒为 **101–105s**(波动 <4%),说明 2–20 天窗口内
  逐日成本**不随天数上升**——与早期 M3 phase2 在 100 天视野看到的逐日成本上升不同。

原因(读码确认,非猜测,未改码):三类历史集合均有界——
`Sim.rumorRetentionDays = 3`(按天淘汰,`rumorCapacity = 16384`/世界)、
`maxAnnalEntries = 128`、`Mind.Memory` 每 NPC 上限。因此 day ~4 后每条曲线进入稳态。

`events` 全程为 0:自主模拟不产生 `InteractionEvent`(仅玩家交互写入),符合预期,非泄漏信号。

### 3) 是否有建议的最小修复?——**不修**

所有指标未越界:live set 有界、RSS 有界且回落、随 N 线性、无 NaN(判据门在 M3R 另测)。
本 slice 只读,不改任何 Kernel/Sim。

观察项(留档,不在本 slice 处理):
- M3 phase2 在 100 天视野的逐日成本上升未在 20 天窗口复现;若需定位需另设 >20 天采样。
- 10 世界并行时单世界墙钟约为串行的 3.0×(GC/CPU 争用),16 核下仍远低于预算。

## 复现

```bash
# 控制组(前台)
dotnet src/LivingVillage.Headless/bin/Release/net8.0/LivingVillage.Headless.dll \
  --profile-multiworld 1 20 30 2 42

# 主运行(独立 systemd --user transient unit,见 deploy/living-village-multiworld.service)
TS=$(date -u +%Y%m%dT%H%M%SZ)
DIR=$PWD/artifacts/perf-multiworld/$TS
systemd-run --user --unit=living-village-multiworld \
  --property=Type=simple --property=WorkingDirectory=$PWD \
  --setenv=PMW_REPO=$PWD --setenv=PMW_DIR=$DIR --setenv=PMW_COMMIT=2c559a7 \
  --setenv=PMW_WORLDS=10 --setenv=PMW_DAYS=20 --setenv=PMW_NPCS=30 \
  --setenv=PMW_SAMPLE_EVERY=2 --setenv=PMW_SEED_BASE=42 --setenv=PMW_LOG=$DIR/acceptance.log \
  --property=StandardOutput=append:$DIR/acceptance.log \
  --property=StandardError=append:$DIR/acceptance.log --property=Restart=no \
  /usr/bin/bash $PWD/scripts/profile-multiworld.sh
```