summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Headless/Program.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 21:54:17 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 21:54:17 +0800
commit1f75c8ad138f252c20244e9b690e3cc0ef32bcde (patch)
tree5c7712f3205598351abe0deca761d447c87f4569 /src/LivingVillage.Headless/Program.fs
parent576ffc44bd94cc5ba7fa67bf9e1d71b8c9ba2979 (diff)
downloadliving-village-1f75c8ad138f252c20244e9b690e3cc0ef32bcde.tar.gz
p49: 长局内存 profile 利基 --profile-long-run(逐日采样 RSS + Events/Rumors/Annals/Memory 条目数与估算字节)
Diffstat (limited to 'src/LivingVillage.Headless/Program.fs')
-rw-r--r--src/LivingVillage.Headless/Program.fs63
1 files changed, 63 insertions, 0 deletions
diff --git a/src/LivingVillage.Headless/Program.fs b/src/LivingVillage.Headless/Program.fs
index 7f5dcf5..23434d0 100644
--- a/src/LivingVillage.Headless/Program.fs
+++ b/src/LivingVillage.Headless/Program.fs
@@ -540,6 +540,67 @@ let runRumorBench (argv: string[]) : int =
Console.Out.Flush()
0
+/// `--profile-long-run [days] [sampleEveryDays] [npcs] [seed]`:长局内存 profile(只读,不改模拟行为)。
+/// 逐模拟日推进,每 `sampleEveryDays` 天采样并流式打印+flush:
+/// * RSS:GC.GetTotalMemory(true)(GC live set)与 Process.WorkingSet64(OS RSS);
+/// * 三类历史集合 Events / Rumors / Annals 的条目数与估算字节;
+/// * NPC Mind.Memory 总条目数与估算字节;
+/// * 首个与最后一个采样点附带世界 digest,证明确定性不被 profile 影响。
+/// 估算字节为保守常数模型(InteractionEvent≈48B、RumorEvent≈112B、AnnalEntry≈96B+Summary*2、
+/// MemoryEvent≈72B),仅作数量级参考;ground truth 是 GC live set 与 WorkingSet。
+let runProfileLongRun (argv: string[]) : int =
+ let argInt64 index (dflt: int64) =
+ if argv.Length > index then
+ match Int64.TryParse argv.[index] with
+ | true, v when v > 0L -> v
+ | _ -> dflt
+ else dflt
+ let days = argInt64 1 40L
+ let sampleEvery = argInt64 2 4L
+ let npcs = int (argInt64 3 (int64 Sim.npcCount))
+ let seed = if argv.Length > 4 then (match UInt64.TryParse argv.[4] with | true, v -> v | _ -> 42UL) else 42UL
+ let mb (bytes: int64) = float bytes / (1024.0 * 1024.0)
+ let proc = System.Diagnostics.Process.GetCurrentProcess()
+ printfn "long_run_start days=%d sample_every_days=%d npcs=%d seed=%d ticks_per_day=%d" days sampleEvery npcs seed ticksPerDay
+ Console.Out.Flush()
+ let mutable world = Sim.initialWorldN seed npcs
+ let mutable driver = { Rng = Rng.ofSeed seed; Until = segmentTicks; Input = zeroInput }
+ let sw = System.Diagnostics.Stopwatch.StartNew()
+ let sample (day: int64) (withDigest: bool) =
+ let gcLive = System.GC.GetTotalMemory true
+ proc.Refresh()
+ let ws = proc.WorkingSet64
+ let events = world.Events.Length
+ let rumors = world.Rumors.Length
+ let annals = world.Annals.Length
+ let memEntries = world.Npcs |> Array.sumBy (fun n -> n.Mind.Memory.Length)
+ let annalsBytes = world.Annals |> List.sumBy (fun a -> 96 + a.Summary.Length * 2)
+ let digest = if withDigest then LivingVillage.Headless.PerformanceProbe.worldDigest world else "-"
+ printfn
+ "long_run_sample day=%d tick=%d gc_live_mb=%.2f working_set_mb=%.2f events=%d rumors=%d annals=%d memory_entries=%d est_events_kb=%.1f est_rumors_kb=%.1f est_annals_kb=%.1f est_memory_kb=%.1f elapsed_s=%.1f digest=%s"
+ day world.Tick (mb gcLive) (mb ws) events rumors annals memEntries
+ (float (events * 48) / 1024.0) (float (rumors * 112) / 1024.0) (float annalsBytes / 1024.0)
+ (float (memEntries * 72) / 1024.0) sw.Elapsed.TotalSeconds digest
+ Console.Out.Flush()
+ sample 0L true
+ let mutable dayNo = 0L
+ while dayNo < days do
+ dayNo <- dayNo + 1L
+ let dayEnd = dayNo * ticksPerDay
+ while world.Tick < dayEnd do
+ let d, input =
+ if world.Tick < driver.Until then driver, driver.Input
+ else
+ let d = nextSegment driver.Rng driver.Until
+ d, d.Input
+ driver <- d
+ world <- Sim.step { Input = input } world
+ if dayNo % sampleEvery = 0L || dayNo = days then sample dayNo false
+ let finalDigest = LivingVillage.Headless.PerformanceProbe.worldDigest world
+ printfn "long_run_done days=%d tick=%d final_digest=%s elapsed_s=%.1f" dayNo world.Tick finalDigest sw.Elapsed.TotalSeconds
+ Console.Out.Flush()
+ if world.Tick = days * ticksPerDay then 0 else 1
+
let runExisting (argv: string[]) =
let rec parse
(i: int)
@@ -632,5 +693,7 @@ let main argv =
runCostProbe (parseCostLadder argv)
elif argv.Length >= 1 && argv.[0] = "--rumor-bench" then
runRumorBench argv
+ elif argv.Length >= 1 && argv.[0] = "--profile-long-run" then
+ runProfileLongRun argv
else
runExisting argv