docs(perf): preserve runtime benchmark evidence
Record the sanitized six-minute CPU/GPU/memory audit, compact machine-readable metrics, checkpoint plateaus, attribution, reproduction method, and raw-artifact retention policy. Link the evidence from the consolidated architecture plan and roadmap without committing binary traces or session metadata. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
944ee55584
commit
e627d9a403
5 changed files with 776 additions and 0 deletions
|
|
@ -1573,6 +1573,8 @@ port in any phase — no separate listing here.
|
||||||
> memory audit reproduced a larger portal allocation/GC storm (approximately
|
> memory audit reproduced a larger portal allocation/GC storm (approximately
|
||||||
> 204 MiB in one frame, 276 ms maximum frame, 38 Gen-2 collections) while
|
> 204 MiB in one frame, 276 ms maximum frame, 38 Gen-2 collections) while
|
||||||
> confirming stable entity/effect/GPU plateaus and low steady GPU utilization.
|
> confirming stable entity/effect/GPU plateaus and low steady GPU utilization.
|
||||||
|
> The complete sanitized benchmark report is
|
||||||
|
> [`2026-07-24-runtime-performance-audit.md`](../research/2026-07-24-runtime-performance-audit.md).
|
||||||
> The user requested a detailed modern architecture plan. The resulting
|
> The user requested a detailed modern architecture plan. The resulting
|
||||||
> [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md)
|
> [`2026-07-24-modern-runtime-architecture.md`](2026-07-24-modern-runtime-architecture.md)
|
||||||
> unifies the unfinished MP pak/render-world work with future Track LH, preserves
|
> unifies the unfinished MP pak/render-world work with future Track LH, preserves
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ visibility ports, and all behavior already accepted by the user.
|
||||||
|
|
||||||
## 1. Why the architecture should change
|
## 1. Why the architecture should change
|
||||||
|
|
||||||
|
The complete methodology, attribution, sanitized checkpoint data, and raw-
|
||||||
|
artifact retention policy are recorded in
|
||||||
|
[`../research/2026-07-24-runtime-performance-audit.md`](../research/2026-07-24-runtime-performance-audit.md).
|
||||||
|
|
||||||
The 2026-07-24 six-minute, nine-stop connected route established:
|
The 2026-07-24 six-minute, nine-stop connected route established:
|
||||||
|
|
||||||
| Observation | Measurement | Architectural implication |
|
| Observation | Measurement | Architectural implication |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
sequence,name,loaded_landblocks,world_entities,animated_entities,particle_emitters,particles,effect_owners,tracked_gpu_mib,managed_mib,managed_committed_mib,fps,frame_ms,reveal_invariant_failures
|
||||||
|
1,caul-baseline,625,21031,18,1712,4562,20896,275.3,239.1,503.3,31.999,31.251,1
|
||||||
|
2,sawato-baseline,625,6659,25,586,3021,5379,347.6,258.4,444.0,31.988,31.262,1
|
||||||
|
3,rynthid,525,9404,19,83,708,9364,362.0,280.2,544.1,32.031,31.220,1
|
||||||
|
4,aerlinthe,374,6660,4,2677,76336,6634,335.0,164.3,564.2,31.986,31.264,1
|
||||||
|
5,sawato-return,625,6659,25,563,2666,5380,345.6,217.7,573.0,32.014,31.237,1
|
||||||
|
6,holtburg,625,10383,51,209,328,10094,417.8,948.6,954.5,32.009,31.242,1
|
||||||
|
7,caul-return,625,21031,18,1714,4530,20901,439.2,260.0,1242.1,32.040,31.211,2
|
||||||
|
8,sawato-plateau,625,6667,32,581,2808,5389,455.2,265.5,1142.6,31.999,31.251,2
|
||||||
|
9,caul-plateau,625,21025,13,1720,4502,20896,430.7,219.7,830.6,32.025,31.225,3
|
||||||
|
159
docs/research/2026-07-24-runtime-performance-audit-summary.json
Normal file
159
docs/research/2026-07-24-runtime-performance-audit-summary.json
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
{
|
||||||
|
"schema": "acdream.runtime-performance-audit.v1",
|
||||||
|
"capture_date": "2026-07-24",
|
||||||
|
"benchmark_commit": "2c3da8e153123240a289417856d0f937887cdace",
|
||||||
|
"report_only": true,
|
||||||
|
"runtime_changed": false,
|
||||||
|
"duration_seconds": 359,
|
||||||
|
"environment": {
|
||||||
|
"configuration": "Release",
|
||||||
|
"developer_ui": false,
|
||||||
|
"retail_ui": true,
|
||||||
|
"frame_profiler": true,
|
||||||
|
"presentation": "normal capped",
|
||||||
|
"display_path": "Remote Desktop",
|
||||||
|
"display_width": 2056,
|
||||||
|
"display_height": 1290,
|
||||||
|
"display_refresh_hz": 32,
|
||||||
|
"cpu": "AMD Ryzen 7 9800X3D",
|
||||||
|
"physical_cores": 8,
|
||||||
|
"logical_processors": 16,
|
||||||
|
"gpu": "AMD Radeon RX 9070 XT",
|
||||||
|
"gpu_driver": "32.0.31021.5001",
|
||||||
|
"os": "Windows NT 10.0.26200.0",
|
||||||
|
"dotnet_sdk": "10.0.300"
|
||||||
|
},
|
||||||
|
"route": [
|
||||||
|
"caul-baseline",
|
||||||
|
"sawato-baseline",
|
||||||
|
"rynthid",
|
||||||
|
"aerlinthe",
|
||||||
|
"sawato-return",
|
||||||
|
"holtburg",
|
||||||
|
"caul-return",
|
||||||
|
"sawato-plateau",
|
||||||
|
"caul-plateau"
|
||||||
|
],
|
||||||
|
"frame": {
|
||||||
|
"stable_fps": 32.0,
|
||||||
|
"stable_wall_frame_ms": 31.2,
|
||||||
|
"steady_update_median_ms_min": 0.4,
|
||||||
|
"steady_update_median_ms_max": 1.3,
|
||||||
|
"steady_update_p95_ms_min": 0.5,
|
||||||
|
"steady_update_p95_ms_max": 2.0,
|
||||||
|
"maximum_frame_ms": 276.4,
|
||||||
|
"worst_window_p95_ms": 52.6,
|
||||||
|
"worst_window_p99_ms": 147.0,
|
||||||
|
"maximum_update_p95_ms": 37.5,
|
||||||
|
"maximum_upload_p95_ms": 3.5,
|
||||||
|
"maximum_single_frame_allocation_mib": 203.6
|
||||||
|
},
|
||||||
|
"process_samples": {
|
||||||
|
"sample_count": 87,
|
||||||
|
"cpu_normalized_percent": {
|
||||||
|
"p50": 0.05,
|
||||||
|
"p95": 1.3,
|
||||||
|
"max": 2.46
|
||||||
|
},
|
||||||
|
"cpu_one_core_percent": {
|
||||||
|
"p50": 0.8,
|
||||||
|
"p95": 20.8,
|
||||||
|
"max": 39.4
|
||||||
|
},
|
||||||
|
"gpu_3d_percent": {
|
||||||
|
"p50": 7.6,
|
||||||
|
"p95": 15.9,
|
||||||
|
"max": 16.8
|
||||||
|
},
|
||||||
|
"working_set_mib": {
|
||||||
|
"min": 776.0,
|
||||||
|
"max": 1661.9
|
||||||
|
},
|
||||||
|
"private_mib": {
|
||||||
|
"min": 1286.4,
|
||||||
|
"max": 2269.1
|
||||||
|
},
|
||||||
|
"gpu_dedicated_mib": {
|
||||||
|
"min": 632.5,
|
||||||
|
"max": 818.0
|
||||||
|
},
|
||||||
|
"handles": {
|
||||||
|
"min": 680,
|
||||||
|
"max": 704
|
||||||
|
},
|
||||||
|
"threads": {
|
||||||
|
"min": 26,
|
||||||
|
"max": 34
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime_counters": {
|
||||||
|
"allocation_rate_average_bytes_per_second": 40791000,
|
||||||
|
"allocation_rate_maximum_bytes_per_second": 2386818304,
|
||||||
|
"total_sampled_allocation_bytes": 14643958472,
|
||||||
|
"gc_pause_total_seconds": 2.07,
|
||||||
|
"gc_pause_maximum_one_second_window_ms": 256,
|
||||||
|
"gen0_collections": 99,
|
||||||
|
"gen1_collections": 105,
|
||||||
|
"gen2_collections": 38,
|
||||||
|
"lock_contentions_total": 4614,
|
||||||
|
"lock_contentions_maximum_per_second": 952,
|
||||||
|
"threadpool_queue_maximum": 0,
|
||||||
|
"working_set_maximum_bytes": 1748021248,
|
||||||
|
"managed_committed_maximum_bytes": 1360900096,
|
||||||
|
"loh_size_maximum_bytes": 807548768,
|
||||||
|
"loh_fragmentation_maximum_bytes": 524062256,
|
||||||
|
"argument_out_of_range_exceptions": 4520,
|
||||||
|
"socket_exceptions": 1168
|
||||||
|
},
|
||||||
|
"final_heap": {
|
||||||
|
"live_bytes": 203771023,
|
||||||
|
"live_mib": 194.3,
|
||||||
|
"live_objects": 1314128,
|
||||||
|
"largest_categories_mib": {
|
||||||
|
"System.Byte[]": 27.5,
|
||||||
|
"DeferredAlphaInstance[]": 5.0,
|
||||||
|
"System.Numerics.Matrix4x4[]": 4.6,
|
||||||
|
"System.Int32[]": 4.2,
|
||||||
|
"CachedBatch[]": 4.2,
|
||||||
|
"System.Single[]": 4.0,
|
||||||
|
"WorldEntity": 3.9,
|
||||||
|
"InstanceLightSet[]": 3.5,
|
||||||
|
"ParticleInstance[]": 3.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"focused_exception_trace": {
|
||||||
|
"exceptions": 1587,
|
||||||
|
"lost_events": 0,
|
||||||
|
"argument_out_of_range_exceptions": 1335,
|
||||||
|
"socket_exceptions": 252,
|
||||||
|
"primary_argument_exception_path": "LivePresentationComposition.ResolveActivation -> RuntimeDatCollection.Get<Setup> -> DatDatabase.TryGet -> Setup.Unpack",
|
||||||
|
"socket_exception_path": "WorldSession.NetReceiveLoop -> NetClientWorldSessionTransport.Receive -> Socket.ReceiveFrom"
|
||||||
|
},
|
||||||
|
"cpu_trace_inclusive_percent": {
|
||||||
|
"ManualResetEventSlim.Wait": 28.79,
|
||||||
|
"WaitHandle.WaitOneNoCheck": 21.68,
|
||||||
|
"LowLevelLifoSemaphore.WaitForSignal": 20.48,
|
||||||
|
"Socket.ReceiveFrom": 7.23,
|
||||||
|
"FramePacingController.CompleteFrame": 6.44,
|
||||||
|
"RenderFrameOrchestrator": 0.6,
|
||||||
|
"WorldSceneRenderer": 0.48,
|
||||||
|
"RetailPViewRenderer.DrawInside": 0.44,
|
||||||
|
"WbDrawDispatcher.Draw": 0.19,
|
||||||
|
"UpdateFrameOrchestrator": 0.15,
|
||||||
|
"MeshExtractor.PrepareMeshData": 0.13
|
||||||
|
},
|
||||||
|
"conclusions": {
|
||||||
|
"unbounded_managed_entity_or_particle_leak_reproduced": false,
|
||||||
|
"unbounded_tracked_gpu_resource_leak_reproduced": false,
|
||||||
|
"portal_allocation_gc_storm_reproduced": true,
|
||||||
|
"gpu_saturated_in_capped_rdp_capture": false,
|
||||||
|
"internal_gpu_frame_timer_valid_under_cap": false,
|
||||||
|
"viewport_before_ready_failures": 3
|
||||||
|
},
|
||||||
|
"raw_artifacts": {
|
||||||
|
"tracked_in_git": false,
|
||||||
|
"file_count": 96,
|
||||||
|
"total_mib": 157.88,
|
||||||
|
"exclusion_reason": "Binary/noisy artifacts contain environment-specific process metadata and would permanently inflate Git history."
|
||||||
|
}
|
||||||
|
}
|
||||||
601
docs/research/2026-07-24-runtime-performance-audit.md
Normal file
601
docs/research/2026-07-24-runtime-performance-audit.md
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
# Runtime CPU/GPU/memory performance audit
|
||||||
|
|
||||||
|
**Capture date:** 2026-07-24
|
||||||
|
|
||||||
|
**Benchmark commit:** `2c3da8e153123240a289417856d0f937887cdace`
|
||||||
|
|
||||||
|
**Status:** Complete, report-only audit. No runtime code, settings, or game
|
||||||
|
behavior were changed during the benchmark.
|
||||||
|
|
||||||
|
**Compact data:**
|
||||||
|
|
||||||
|
- [`2026-07-24-runtime-performance-audit-summary.json`](2026-07-24-runtime-performance-audit-summary.json)
|
||||||
|
- [`2026-07-24-runtime-performance-audit-checkpoints.csv`](2026-07-24-runtime-performance-audit-checkpoints.csv)
|
||||||
|
|
||||||
|
**Architecture response:**
|
||||||
|
[`../plans/2026-07-24-modern-runtime-architecture.md`](../plans/2026-07-24-modern-runtime-architecture.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive result
|
||||||
|
|
||||||
|
Steady capped play was inexpensive. Portal transitions were not.
|
||||||
|
|
||||||
|
The client maintained approximately 32 FPS on a 32 Hz Remote Desktop display.
|
||||||
|
Normal update work was approximately 0.4–1.3 ms median, and external GPU 3D use
|
||||||
|
was 7.6% median. The dominant performance failure was the burst of runtime
|
||||||
|
content preparation, publication, and retirement performed around portal
|
||||||
|
transitions:
|
||||||
|
|
||||||
|
- Up to approximately 204 MiB allocated in one frame.
|
||||||
|
- Approximately 2.22 GiB/s maximum managed allocation rate.
|
||||||
|
- 38 Gen-2 collections over the six-minute route.
|
||||||
|
- 256 ms maximum recorded GC pause.
|
||||||
|
- 276 ms maximum frame.
|
||||||
|
|
||||||
|
The final live managed heap was approximately 194 MiB even though the process
|
||||||
|
had previously reached 1.66 GiB working set and 2.27 GiB private bytes. Entity,
|
||||||
|
particle, effect, and tracked GPU counts returned to repeatable plateaus when
|
||||||
|
locations were revisited. The audit therefore did **not** reproduce an
|
||||||
|
unbounded logical-object or GPU-resource leak. It reproduced excessive
|
||||||
|
allocation and retained high-water memory.
|
||||||
|
|
||||||
|
Allocation stacks identify runtime mesh/texture preparation as the largest
|
||||||
|
portal source. Update-thread landblock publication and retirement then amplify
|
||||||
|
the burst with complete dictionary/set/sort work and unmetered per-entity
|
||||||
|
operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Test conditions
|
||||||
|
|
||||||
|
### Hardware and host
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|---|---|
|
||||||
|
| CPU | AMD Ryzen 7 9800X3D, 8 cores / 16 logical processors, up to 4.7 GHz |
|
||||||
|
| GPU | AMD Radeon RX 9070 XT |
|
||||||
|
| GPU driver | `32.0.31021.5001` |
|
||||||
|
| Active display path | Microsoft Remote Display Adapter |
|
||||||
|
| Active resolution | 2056 × 1290 at 32 Hz |
|
||||||
|
| Physical display | 2560 × 1440 at 240 Hz |
|
||||||
|
| OS | Windows NT 10.0.26200.0 |
|
||||||
|
| Runtime | .NET 10 / SDK 10.0.300 |
|
||||||
|
|
||||||
|
### Client configuration
|
||||||
|
|
||||||
|
- Release build.
|
||||||
|
- Normal graphical client.
|
||||||
|
- Retail retained UI.
|
||||||
|
- Developer UI disabled.
|
||||||
|
- Normal capped presentation.
|
||||||
|
- `ACDREAM_FRAME_PROF=1` enabled for observational frame metrics.
|
||||||
|
- Local ACE server.
|
||||||
|
- Graceful client shutdown.
|
||||||
|
|
||||||
|
The RDP display and normal presentation cap prevent this capture from
|
||||||
|
establishing uncapped maximum FPS or maximum GPU throughput. Capped and uncapped
|
||||||
|
local-display results must remain separate in future comparisons.
|
||||||
|
|
||||||
|
### Route
|
||||||
|
|
||||||
|
The existing connected R6 route produced nine canonical checkpoints:
|
||||||
|
|
||||||
|
1. Caul baseline.
|
||||||
|
2. Sawato baseline.
|
||||||
|
3. Rynthid world edge.
|
||||||
|
4. Aerlinthe.
|
||||||
|
5. Sawato return.
|
||||||
|
6. Holtburg.
|
||||||
|
7. Caul return.
|
||||||
|
8. Sawato plateau.
|
||||||
|
9. Caul plateau.
|
||||||
|
|
||||||
|
The route includes portal transitions, location revisits, camera turns, input
|
||||||
|
exercises, stable dwell periods, checkpoint capture, and graceful disconnect.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Measurement methods
|
||||||
|
|
||||||
|
The primary process ran for approximately 359 seconds. Measurements combined:
|
||||||
|
|
||||||
|
- The permanent `[frame-prof]` ring profiler.
|
||||||
|
- `System.Runtime` counters through `dotnet-counters`.
|
||||||
|
- Windows per-process CPU, memory, handle, thread, I/O, and GPU-engine samples.
|
||||||
|
- A 30-second CPU-sampling EventPipe trace at Aerlinthe.
|
||||||
|
- A final GC heap dump.
|
||||||
|
- A short allocation-sampling trace covering startup and Caul.
|
||||||
|
- A short exception-stack trace covering a connected route segment.
|
||||||
|
- Canonical world lifecycle/resource checkpoint JSON.
|
||||||
|
|
||||||
|
The allocation trace reports sampled allocation intervals. A type attached to
|
||||||
|
an allocation tick is not proof that a single object of that type had the full
|
||||||
|
sampled byte count. Stack attribution is the reliable part of that capture.
|
||||||
|
|
||||||
|
The CPU trace was collected while counters were also attached. It is suitable
|
||||||
|
for ranking active code, not for absolute standalone CPU percentages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Headline measurements
|
||||||
|
|
||||||
|
| Measurement | Result |
|
||||||
|
|---|---:|
|
||||||
|
| Stable capped rate | approximately 32 FPS / 31.2 ms wall frame |
|
||||||
|
| Steady update work | approximately 0.4–1.3 ms median |
|
||||||
|
| Normal update p95 | approximately 0.5–2.0 ms |
|
||||||
|
| CPU normalized p50 / p95 / max | 0.05% / 1.30% / 2.46% |
|
||||||
|
| CPU as one-core p50 / p95 / max | 0.8% / 20.8% / 39.4% |
|
||||||
|
| GPU 3D p50 / p95 / max | 7.6% / 15.9% / 16.8% |
|
||||||
|
| Working set range | 776.0–1,661.9 MiB |
|
||||||
|
| Private-byte range | 1,286.4–2,269.1 MiB |
|
||||||
|
| Dedicated GPU range | 632.5–818.0 MiB |
|
||||||
|
| Handle range | 680–704 |
|
||||||
|
| Thread range | 26–34 |
|
||||||
|
| Managed allocation average | approximately 40.79 MB/s |
|
||||||
|
| Managed allocation maximum | 2,386,818,304 B/s |
|
||||||
|
| Total sampled managed allocation | 14,643,958,472 B |
|
||||||
|
| Maximum single-frame allocation | approximately 203.6 MiB |
|
||||||
|
| Maximum frame | 276.4 ms |
|
||||||
|
| Worst five-second p95 / p99 | 52.6 ms / 147 ms |
|
||||||
|
| Maximum update p95 | 37.5 ms |
|
||||||
|
| Maximum upload p95 | 3.5 ms |
|
||||||
|
| Total GC pause time | approximately 2.07 s |
|
||||||
|
| Maximum one-second GC pause | 256 ms |
|
||||||
|
| Collections Gen0 / Gen1 / Gen2 | 99 / 105 / 38 |
|
||||||
|
| Lock contentions | 4,614 total; 952/s maximum |
|
||||||
|
| Thread-pool queue | 0 throughout capture |
|
||||||
|
|
||||||
|
Steady per-frame allocation varied by scene:
|
||||||
|
|
||||||
|
- Sawato: approximately 27–28 KiB/frame.
|
||||||
|
- Caul: approximately 42–44 KiB/frame.
|
||||||
|
- Holtburg: approximately 53.5 KiB/frame outside the portal burst.
|
||||||
|
|
||||||
|
Portal windows produced maximum frames of approximately 203.6 MiB, 141.6 MiB,
|
||||||
|
141.5 MiB, 58.5 MiB, and 35.1 MiB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Checkpoint and leak analysis
|
||||||
|
|
||||||
|
The complete sanitized checkpoint table is in the companion CSV. Representative
|
||||||
|
revisit comparisons:
|
||||||
|
|
||||||
|
| Location | Visit | Landblocks | Entities | Emitters | Particles | Tracked GPU MiB |
|
||||||
|
|---|---|---:|---:|---:|---:|---:|
|
||||||
|
| Caul | baseline | 625 | 21,031 | 1,712 | 4,562 | 275.3 |
|
||||||
|
| Caul | return | 625 | 21,031 | 1,714 | 4,530 | 439.2 |
|
||||||
|
| Caul | plateau | 625 | 21,025 | 1,720 | 4,502 | 430.7 |
|
||||||
|
| Sawato | baseline | 625 | 6,659 | 586 | 3,021 | 347.6 |
|
||||||
|
| Sawato | return | 625 | 6,659 | 563 | 2,666 | 345.6 |
|
||||||
|
| Sawato | plateau | 625 | 6,667 | 581 | 2,808 | 455.2 |
|
||||||
|
|
||||||
|
Entity, emitter, and particle populations converged. GPU residence warmed and
|
||||||
|
then plateaued rather than increasing without bound. The different Sawato GPU
|
||||||
|
plateau reflects the process-wide cache working set accumulated through the
|
||||||
|
complete route, not an increasing Sawato entity population.
|
||||||
|
|
||||||
|
All three Caul materializations recorded `viewport-before-ready` invariant
|
||||||
|
failures. This is separate evidence that destination work and reveal are not
|
||||||
|
currently one atomic generation-ready transaction.
|
||||||
|
|
||||||
|
### Final managed heap
|
||||||
|
|
||||||
|
- Live heap: 203,771,023 B, approximately 194.3 MiB.
|
||||||
|
- Live objects: 1,314,128.
|
||||||
|
- Last observed committed managed memory: approximately 830.6 MiB.
|
||||||
|
|
||||||
|
Large retained categories included:
|
||||||
|
|
||||||
|
| Category | Approximate live size |
|
||||||
|
|---|---:|
|
||||||
|
| `System.Byte[]` | 27.5 MiB |
|
||||||
|
| `DeferredAlphaInstance[]` | 5.0 MiB |
|
||||||
|
| `Matrix4x4[]` | 4.6 MiB |
|
||||||
|
| `Int32[]` | 4.2 MiB |
|
||||||
|
| `CachedBatch[]` | 4.2 MiB |
|
||||||
|
| `Single[]` | 4.0 MiB |
|
||||||
|
| `WorldEntity` objects | 3.9 MiB |
|
||||||
|
| `InstanceLightSet[]` | 3.5 MiB |
|
||||||
|
| Fixed `ParticleInstance[]` | 3.5 MiB |
|
||||||
|
|
||||||
|
There were approximately 20,000 records in several static presentation
|
||||||
|
registries, including pose, script activation, static owner state, and effect
|
||||||
|
profile tables. Individually they are modest, but together they add portal
|
||||||
|
create/teardown work and several MiB of retained state.
|
||||||
|
|
||||||
|
The gap between 194 MiB live heap and much larger committed/private memory is
|
||||||
|
primarily GC segment high-water, fragmentation, native/driver allocations,
|
||||||
|
mapped/cached data, and GPU resource residence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. CPU attribution
|
||||||
|
|
||||||
|
Most CPU samples represented waits and presentation pacing:
|
||||||
|
|
||||||
|
| Stack/category | Inclusive share of all samples |
|
||||||
|
|---|---:|
|
||||||
|
| `ManualResetEventSlim.Wait` | 28.79% |
|
||||||
|
| `WaitHandle.WaitOneNoCheck` | 21.68% |
|
||||||
|
| `LowLevelLifoSemaphore.WaitForSignal` | 20.48% |
|
||||||
|
| `Socket.ReceiveFrom` | 7.23% |
|
||||||
|
| `FramePacingController.CompleteFrame` | 6.44% |
|
||||||
|
|
||||||
|
Named active App work was comparatively small in this capped scene:
|
||||||
|
|
||||||
|
| App path | Inclusive share of all samples |
|
||||||
|
|---|---:|
|
||||||
|
| `RenderFrameOrchestrator` | 0.60% |
|
||||||
|
| `WorldSceneRenderer` | 0.48% |
|
||||||
|
| `RetailPViewRenderer.DrawInside` | 0.44% |
|
||||||
|
| `DrawLandscapeThroughOutsideView` | 0.33% |
|
||||||
|
| `DrawLandscapeSlice` | 0.23% |
|
||||||
|
| `DatDatabaseWrapper.TryGet` | 0.21% |
|
||||||
|
| `WbDrawDispatcher.Draw` | 0.19% |
|
||||||
|
| `UpdateFrameOrchestrator` | 0.15% |
|
||||||
|
| `MeshExtractor.PrepareMeshData` | 0.13% |
|
||||||
|
|
||||||
|
The largest named exclusive App sites were `InteriorEntityPartition.Partition`,
|
||||||
|
`WbDrawDispatcher.Draw`, terrain clip AABB testing, PView outside work, and
|
||||||
|
deferred-alpha preparation. These are small under the 32 Hz cap but scale with
|
||||||
|
uncapped frame rate.
|
||||||
|
|
||||||
|
There was no evidence of a spinning thread-pool worker or a growing thread-pool
|
||||||
|
queue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. GPU analysis and timing limitation
|
||||||
|
|
||||||
|
External GPU 3D utilization was low in this capture, so the client was not
|
||||||
|
GPU-saturated. Tracked GPU resources warmed from approximately 275 MiB to
|
||||||
|
approximately 431 MiB, with OS-reported dedicated memory reaching 818 MiB.
|
||||||
|
Approximately 599 tracked textures and 43–48 atlas arrays were resident near the
|
||||||
|
end of the route.
|
||||||
|
|
||||||
|
The difference between tracked and OS-reported GPU memory can include driver
|
||||||
|
allocation, texture-array slack, alignment, metadata, framebuffer resources,
|
||||||
|
and resources not covered by acdream's tracker.
|
||||||
|
|
||||||
|
The internal `gpu_ms` value is not valid active-render time under a cap.
|
||||||
|
`GpuFrameTimer.FrameBoundary` starts at one frame boundary and completes at the
|
||||||
|
next, so the GPU query includes idle/pacing gaps. Its 23–30 ms result must not be
|
||||||
|
interpreted as GPU saturation. Correct pass/submission timestamp measurement is
|
||||||
|
Slice A of the modern runtime plan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Allocation and exception attribution
|
||||||
|
|
||||||
|
### Runtime mesh and texture preparation
|
||||||
|
|
||||||
|
The largest sampled allocation stacks were:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AcDream.Content.MeshExtractor.AddSurfaceToBatch
|
||||||
|
-> PrepareGfxObjMeshData
|
||||||
|
-> PrepareMeshData
|
||||||
|
-> AcDream.App.Rendering.Wb.ObjectMeshManager.ProcessQueue
|
||||||
|
```
|
||||||
|
|
||||||
|
The path decodes compressed and palette textures into RGBA arrays, clones cached
|
||||||
|
pixels for surface-local alpha changes, constructs vertex/index collections,
|
||||||
|
and produces upload batches. Rebuilding those products during portals creates
|
||||||
|
large short-lived graphs even when the same world content has been seen before.
|
||||||
|
|
||||||
|
Relevant implementation:
|
||||||
|
[`../src/AcDream.Content/MeshExtractor.cs`](../../src/AcDream.Content/MeshExtractor.cs).
|
||||||
|
|
||||||
|
### Invalid Setup probes
|
||||||
|
|
||||||
|
The focused exception trace captured 1,587 exceptions with no lost events:
|
||||||
|
|
||||||
|
- 1,335 `ArgumentOutOfRangeException`.
|
||||||
|
- 252 `SocketException`.
|
||||||
|
|
||||||
|
The DAT exceptions followed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
QualifiedDataId<T>.Unpack
|
||||||
|
-> Setup.Unpack
|
||||||
|
-> DatDatabase.TryGet
|
||||||
|
-> RuntimeDatCollection.Get<Setup>
|
||||||
|
-> LivePresentationComposition.ResolveActivation
|
||||||
|
-> EntityScriptActivator.OnCreate
|
||||||
|
-> GpuWorldState.ActivateLandblockPresentation
|
||||||
|
-> StreamingController.ApplyResult
|
||||||
|
```
|
||||||
|
|
||||||
|
`ResolveActivation` attempts `Get<Setup>` for every static source ID and catches
|
||||||
|
all exceptions. Many IDs are GfxObj IDs, not Setup IDs. The mesh path already
|
||||||
|
contains the required DID-prefix validation.
|
||||||
|
|
||||||
|
Relevant implementation:
|
||||||
|
[`../src/AcDream.App/Composition/LivePresentationComposition.cs`](../../src/AcDream.App/Composition/LivePresentationComposition.cs).
|
||||||
|
|
||||||
|
### Network receive timeout
|
||||||
|
|
||||||
|
`NetClient` configures a receive timeout and catches `SocketError.TimedOut` as a
|
||||||
|
normal heartbeat every approximately 250 ms. Every send also materializes
|
||||||
|
`datagram.ToArray()`.
|
||||||
|
|
||||||
|
Relevant implementation:
|
||||||
|
[`../src/AcDream.Core.Net/NetClient.cs`](../../src/AcDream.Core.Net/NetClient.cs).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Ranked findings
|
||||||
|
|
||||||
|
### P0 — Portal content preparation and publication storm
|
||||||
|
|
||||||
|
**Sources**
|
||||||
|
|
||||||
|
- `MeshExtractor` runtime decode and texture cloning.
|
||||||
|
- `StreamingController.DrainAndApply`.
|
||||||
|
- `LandblockRetirementCoordinator.RunForEachEntity`.
|
||||||
|
- Landblock render, physics, static presentation, effect, light, and plugin
|
||||||
|
publication.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
- 204 MiB maximum single-frame allocation.
|
||||||
|
- 276 ms maximum frame.
|
||||||
|
- Gen-2 GC and committed-heap high-water.
|
||||||
|
- Destination readiness failures.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
- Finish the deduplicated baked pak and consume prepared data at runtime.
|
||||||
|
- Keep visibility/collision detachment immediate.
|
||||||
|
- Budget expensive publication/teardown by time, bytes, and entity operations.
|
||||||
|
- Prepare the destination across portal frames and reveal one ready generation.
|
||||||
|
|
||||||
|
**Plan mapping:** Slices B, C, and E.
|
||||||
|
|
||||||
|
### P1 — Exception-driven DAT type resolution
|
||||||
|
|
||||||
|
**Source**
|
||||||
|
|
||||||
|
`LivePresentationComposition.ResolveActivation` parses arbitrary source IDs as
|
||||||
|
Setup and catches failure.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
Thousands of constructed/thrown exceptions during streaming, avoidable DAT
|
||||||
|
parsing, noisy diagnostics, and main-thread activation work.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Use explicit DID type metadata, non-allocating typed lookup, negative caching,
|
||||||
|
and prepared Setup presentation metadata. Exceptions must represent corruption
|
||||||
|
or failure, not ordinary type selection.
|
||||||
|
|
||||||
|
**Plan mapping:** Slice C.
|
||||||
|
|
||||||
|
### P1 — Complete collection rebuilds during static publication
|
||||||
|
|
||||||
|
**Source**
|
||||||
|
|
||||||
|
`LandblockStaticPresentationPublisher` creates entity/snapshot dictionaries,
|
||||||
|
copies previous keys to a set, and sorts IDs/snapshots per publication.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
Portal allocation and update-thread CPU scale with complete landblock contents.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Carry canonical ordered prepared arrays, use cursor-based publication receipts,
|
||||||
|
and publish versioned deltas rather than reconstructing complete collections.
|
||||||
|
|
||||||
|
**Plan mapping:** Slices C, E, and F.
|
||||||
|
|
||||||
|
### P1 — DAT resolution/cache churn
|
||||||
|
|
||||||
|
**Sources**
|
||||||
|
|
||||||
|
- `DatCollectionAdapter.ResolveId`.
|
||||||
|
- `MeshExtractor.ResolveId().ToList().OrderByDescending().FirstOrDefault()`.
|
||||||
|
- Small bounded typed-object cache plus serialized database access.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
Repeated parsing on revisits, allocation from resolution candidates/LINQ, 4,614
|
||||||
|
lock contentions, and repeated invalid type probes.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Use typed non-allocating resolution, prepared compact metadata, negative/type
|
||||||
|
caches, and remove world render/streaming DAT reads after the pak cutover.
|
||||||
|
|
||||||
|
**Plan mapping:** Slices B and C.
|
||||||
|
|
||||||
|
### P2 — Whole-world render partition every frame
|
||||||
|
|
||||||
|
**Source**
|
||||||
|
|
||||||
|
`InteriorEntityPartition.Partition` scans every entity in every loaded
|
||||||
|
landblock each render frame. Caul had approximately 21,000 entities.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
CPU cost grows with the complete resident world rather than visible cells and
|
||||||
|
changes. Reused lists reduce allocation but not traversal.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Maintain outdoor-static, per-cell, dynamic, translucent, selectable, and light
|
||||||
|
indices incrementally in a render-only scene database. Existing retail PView
|
||||||
|
remains the visible-cell authority.
|
||||||
|
|
||||||
|
**Plan mapping:** Slices F and G.
|
||||||
|
|
||||||
|
### P2 — Unconditional diagnostic and retained-UI work
|
||||||
|
|
||||||
|
**Sources**
|
||||||
|
|
||||||
|
- `WorldSceneDiagnosticsController` performs every-landblock AABB tests without
|
||||||
|
an active debug consumer.
|
||||||
|
- `GpuWorldState.LandblockBounds` uses an iterator.
|
||||||
|
- Retained UI reapplies anchors and walks the overlay tree every frame.
|
||||||
|
- Item cooldown UI scans complete item lists.
|
||||||
|
- Dialog handling snapshots open-order arrays.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
Moderate capped cost, but it scales directly with uncapped FPS and adds small
|
||||||
|
steady allocations.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Reuse renderer facts, skip inactive diagnostics, add UI dirty-layout/event
|
||||||
|
registries, and replace frame iterators/snapshots with borrowed stable views.
|
||||||
|
|
||||||
|
**Plan mapping:** Slice H.
|
||||||
|
|
||||||
|
### P2 — High but bounded GPU/cache residency
|
||||||
|
|
||||||
|
**Sources**
|
||||||
|
|
||||||
|
- Object mesh and texture caches.
|
||||||
|
- Standalone and composite texture arrays.
|
||||||
|
- Fixed 65,536-instance particle CPU/GPU buffer.
|
||||||
|
- Deferred-alpha scratch arrays that grow to scene high-water.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
633–818 MiB dedicated GPU memory during the route, plus driver/native
|
||||||
|
difference from tracked resources.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Introduce typed asset leases and unified accounting, measure atlas occupancy and
|
||||||
|
fragmentation, use generation-safe cost-aware eviction, and shrink or segment
|
||||||
|
large scratch buffers only with hysteresis.
|
||||||
|
|
||||||
|
Do not reduce view distance or particle range as the primary correction.
|
||||||
|
|
||||||
|
**Plan mapping:** Slice D.
|
||||||
|
|
||||||
|
### P3 — Smaller continuous allocations and background overhead
|
||||||
|
|
||||||
|
**Sources**
|
||||||
|
|
||||||
|
- Per-frame `RetailPViewFrameInput`.
|
||||||
|
- Yield iterators and periodic liveness sets.
|
||||||
|
- Full candidate light sorting.
|
||||||
|
- UDP send copies.
|
||||||
|
- Socket timeout exceptions.
|
||||||
|
|
||||||
|
**Impact**
|
||||||
|
|
||||||
|
Not responsible for the portal storm, but measurable at high uncapped FPS or
|
||||||
|
across many headless sessions.
|
||||||
|
|
||||||
|
**Recommendation**
|
||||||
|
|
||||||
|
Use reusable frame views, stable spans, spatial light candidates, span-based
|
||||||
|
socket sends, and cancellable blocking/async receive without exception-driven
|
||||||
|
timeouts.
|
||||||
|
|
||||||
|
**Plan mapping:** Slices H and J.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Recommended execution order
|
||||||
|
|
||||||
|
The audit supports this order:
|
||||||
|
|
||||||
|
1. Correct GPU/pass measurement and checkpoint accounting.
|
||||||
|
2. Complete EnvCell dedup and the full pak bake.
|
||||||
|
3. Cut world mesh/texture/Setup presentation preparation over to prepared data.
|
||||||
|
4. Add explicit CPU/staging/GPU residency ownership and accounting.
|
||||||
|
5. Replace portal budget bypasses with staged cost budgets and one reveal
|
||||||
|
generation.
|
||||||
|
6. Add the incremental render scene and delta submission.
|
||||||
|
7. Remove remaining per-frame UI/diagnostic/network allocation.
|
||||||
|
8. Extract the presentation-independent runtime for Linux/headless use.
|
||||||
|
|
||||||
|
The complete slice definitions and acceptance gates are in the linked modern
|
||||||
|
runtime architecture plan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Reproduction
|
||||||
|
|
||||||
|
### Canonical route
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet build AcDream.slnx -c Release
|
||||||
|
|
||||||
|
$env:ACDREAM_TEST_USER = '<account>'
|
||||||
|
$env:ACDREAM_TEST_PASS = '<password>'
|
||||||
|
$env:ACDREAM_FRAME_PROF = '1'
|
||||||
|
|
||||||
|
.\tools\run-connected-r6-soak.ps1 -Repository (Get-Location).Path -SkipBuild
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not place credentials in committed logs or reports.
|
||||||
|
|
||||||
|
### Runtime counters
|
||||||
|
|
||||||
|
After identifying the client PID:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet-counters collect `
|
||||||
|
--process-id <pid> `
|
||||||
|
--counters System.Runtime `
|
||||||
|
--format csv `
|
||||||
|
--output dotnet-counters.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
### CPU trace
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet-trace collect `
|
||||||
|
--process-id <pid> `
|
||||||
|
--profile cpu-sampling `
|
||||||
|
--duration 00:00:30 `
|
||||||
|
--output cpu.nettrace
|
||||||
|
```
|
||||||
|
|
||||||
|
### Final live heap
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet-gcdump collect --process-id <pid> --output final.gcdump
|
||||||
|
```
|
||||||
|
|
||||||
|
For comparable results:
|
||||||
|
|
||||||
|
- Use the same commit, Release configuration, route, and camera/input script.
|
||||||
|
- Record RDP/capped and local/uncapped modes separately.
|
||||||
|
- Gracefully close the client.
|
||||||
|
- Capture checkpoint JSON before interpreting process memory.
|
||||||
|
- Compare live heap, GC committed memory, tracked GPU bytes, and OS GPU memory
|
||||||
|
separately.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Artifact retention policy
|
||||||
|
|
||||||
|
The local raw capture contained 96 files totaling approximately 157.9 MiB.
|
||||||
|
Heavy and environment-specific artifacts were deliberately excluded from Git:
|
||||||
|
|
||||||
|
- `.nettrace` and generated `.etlx`.
|
||||||
|
- `.gcdump`.
|
||||||
|
- Full `dotnet-counters.csv`.
|
||||||
|
- Complete process/GPU sample CSV.
|
||||||
|
- Full client stdout/stderr.
|
||||||
|
- Generated trace-reader project binaries.
|
||||||
|
- Process IDs, timestamps, absolute local paths, and session-specific logs.
|
||||||
|
|
||||||
|
Those files are valuable for local forensic work but poor Git artifacts: they
|
||||||
|
are binary or noisy, inflate permanent history, are difficult for code-review
|
||||||
|
tools to inspect, and can expose local/session metadata.
|
||||||
|
|
||||||
|
This report plus the compact JSON and checkpoint CSV preserve the benchmark's
|
||||||
|
method, provenance, measurements, interpretation, and decision-driving evidence
|
||||||
|
in a form another engineer or model can review directly.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue