perf: benchmark regional simulation scale
This commit is contained in:
@@ -24,8 +24,24 @@ Loaded-resource discovery has its own bounded runner:
|
|||||||
--script res://tools/benchmark_loaded_resource_discovery.gd
|
--script res://tools/benchmark_loaded_resource_discovery.gd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The regional record/scheduler workload has a separate honest runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/Applications/Godot.app/Contents/MacOS/Godot \
|
||||||
|
--headless --path "$PWD" \
|
||||||
|
--script res://tools/benchmark_regional_scale.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
It constructs five settlements, 20 caravans, 2,000 named people, 50,000
|
||||||
|
aggregate residents, and 2,025 deterministic due jobs. It measures record
|
||||||
|
construction, canonical roundtrip, scheduler construction, and job draining;
|
||||||
|
it does not claim rendered or full-economy throughput.
|
||||||
|
|
||||||
Reviewed captures:
|
Reviewed captures:
|
||||||
|
|
||||||
|
- [Regional scale baseline 01](REGIONAL_SCALE_BASELINE_01.md) records the
|
||||||
|
primitive regional state and deterministic scheduler workload at five
|
||||||
|
settlements, 20 caravans, 2,000 named people, and 50,000 aggregate residents.
|
||||||
- [Simulation scaling baseline 01](SIMULATION_SCALING_BASELINE_01.md) records
|
- [Simulation scaling baseline 01](SIMULATION_SCALING_BASELINE_01.md) records
|
||||||
the first full-fidelity population/history measurements and the bounded next
|
the first full-fidelity population/history measurements and the bounded next
|
||||||
optimization.
|
optimization.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Regional scale baseline 01
|
||||||
|
|
||||||
|
This is the first reviewed capture of workload schema 1,
|
||||||
|
`regional_records_scheduler_v1`. It is a structural headless baseline for the
|
||||||
|
regional record and deterministic due-job contracts—not a claim about rendered
|
||||||
|
gameplay or the complete economy.
|
||||||
|
|
||||||
|
The machine-readable report is in
|
||||||
|
[`regional_scale_baseline_01.json`](regional_scale_baseline_01.json). It was
|
||||||
|
captured with Godot 4.7 on the `Apple_M1_Max_64_GB` development host, seed
|
||||||
|
424242, three default-budget samples, and parity runs at budgets one and
|
||||||
|
unlimited.
|
||||||
|
|
||||||
|
## Fixed workload
|
||||||
|
|
||||||
|
- five locations, settlements, polities, and ring routes;
|
||||||
|
- 20 caravans with 12 named members each;
|
||||||
|
- 2,000 persistent named people;
|
||||||
|
- five aggregate cohorts totaling 50,000 residents;
|
||||||
|
- 2,025 keyed deterministic due jobs;
|
||||||
|
- maximum active-presentation metadata of 40;
|
||||||
|
- 736,115 bytes of canonical regional state and 540,942 bytes of initial
|
||||||
|
scheduler state.
|
||||||
|
|
||||||
|
All records round-tripped through their primitive-only schemas. The fixture
|
||||||
|
contained 52,000 people and 2,734 cargo units without duplicate global IDs or
|
||||||
|
membership errors. Budget one, budget eight, and unlimited execution drained
|
||||||
|
the backlog to zero in exactly the same order and produced final checksum
|
||||||
|
`57c8a8eb9dd48bbea309cdb5654ce508f6e4965b07228217aa55fc43de699a25`.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
| Phase | Median | Samples |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| Fixture construction | 273.79 ms | 272.37, 273.79, 282.07 ms |
|
||||||
|
| Canonical roundtrip | 255.86 ms | 255.33, 255.86, 266.03 ms |
|
||||||
|
| Scheduler construction | 74.10 ms | 73.66, 74.10, 76.50 ms |
|
||||||
|
| Drain 2,025 jobs (budget 8) | 57.47 ms | 55.52, 57.47, 59.51 ms |
|
||||||
|
| Complete measured run | 785.65 ms | 776.58, 785.65, 808.22 ms |
|
||||||
|
|
||||||
|
Budget eight drained the queue in 254 batches. Budget one used 2,025 batches;
|
||||||
|
unlimited used one. Their execution-order, fixture, and final checksums were
|
||||||
|
identical.
|
||||||
|
|
||||||
|
## Interpretation
|
||||||
|
|
||||||
|
The baseline proves that the target record counts fit in a small canonical
|
||||||
|
payload and that job-budget changes do not alter outcomes. It intentionally
|
||||||
|
constructs only 2,025 event-boundary jobs, so the 57.47 ms drain is not a
|
||||||
|
30-day simulation-time throughput result. It also excludes scene loading,
|
||||||
|
navigation, rendering, dialogue/situation queries, market transactions,
|
||||||
|
conflict, event rollups, chunked disk I/O, and live caravan transfers.
|
||||||
|
|
||||||
|
The next benchmark must run scheduled settlement and caravan domain handlers
|
||||||
|
over 30 simulated days. Separately, a recorded weak-PC active-world capture
|
||||||
|
must measure the Low presentation tier with at most 40 detailed visuals and two
|
||||||
|
new navigation requests per frame. These measurements cannot be inferred from
|
||||||
|
this development-host structural result.
|
||||||
|
|
||||||
|
## Capture command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/Applications/Godot.app/Contents/MacOS/Godot \
|
||||||
|
--headless --path "$PWD" \
|
||||||
|
--script res://tools/benchmark_regional_scale.gd -- \
|
||||||
|
--host-label=Apple_M1_Max_64_GB \
|
||||||
|
--output=res://docs/benchmarks/regional_scale_baseline_01.json
|
||||||
|
```
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"benchmark_scope": "regional_data_and_scheduler_only",
|
||||||
|
"budget_parity": {
|
||||||
|
"budget_one_drain_batches": 2025,
|
||||||
|
"budget_one_final_checksum": "57c8a8eb9dd48bbea309cdb5654ce508f6e4965b07228217aa55fc43de699a25",
|
||||||
|
"identical": true,
|
||||||
|
"unlimited_drain_batches": 1,
|
||||||
|
"unlimited_final_checksum": "57c8a8eb9dd48bbea309cdb5654ce508f6e4965b07228217aa55fc43de699a25"
|
||||||
|
},
|
||||||
|
"captured_utc": "2026-08-12T19:36:00Z",
|
||||||
|
"default_budget": {
|
||||||
|
"backlog_final": 0,
|
||||||
|
"backlog_initial": 2025,
|
||||||
|
"backlog_peak": 2025,
|
||||||
|
"drain_batches": 254,
|
||||||
|
"elapsed_usec_median": 785655,
|
||||||
|
"elapsed_usec_samples": [776580, 785655, 808216],
|
||||||
|
"execution_usec_median": 57471,
|
||||||
|
"execution_usec_samples": [55516, 57471, 59508],
|
||||||
|
"fixture_build_usec_median": 273789,
|
||||||
|
"fixture_build_usec_samples": [272365, 273789, 282068],
|
||||||
|
"roundtrip_usec_median": 255860,
|
||||||
|
"roundtrip_usec_samples": [255326, 255860, 266034],
|
||||||
|
"scheduler_build_usec_median": 74100,
|
||||||
|
"scheduler_build_usec_samples": [73657, 74100, 76495]
|
||||||
|
},
|
||||||
|
"default_execution_budget": 8,
|
||||||
|
"engine_version": "4.7-stable (official)",
|
||||||
|
"fixture": {
|
||||||
|
"caravan_count": 20,
|
||||||
|
"cargo_total": 2734.0,
|
||||||
|
"cohort_resident_count": 50000,
|
||||||
|
"detailed_caravan_member_count": 240,
|
||||||
|
"detailed_members_per_caravan": 12,
|
||||||
|
"execution_order_checksum": "38815c49987cf66ec0d78b82d332789f708742959bab83c9feee631d8bbf8174",
|
||||||
|
"final_checksum": "57c8a8eb9dd48bbea309cdb5654ce508f6e4965b07228217aa55fc43de699a25",
|
||||||
|
"fixture_checksum": "5ee2dcda7067b34e1ffc22861fbcf1e6d1897f87523a88fbf216fc9ea6a19ed5",
|
||||||
|
"initial_job_count": 2025,
|
||||||
|
"location_count": 5,
|
||||||
|
"max_active_visuals": 40,
|
||||||
|
"named_person_count": 2000,
|
||||||
|
"regional_state_bytes": 736115,
|
||||||
|
"scheduler_state_bytes": 540942,
|
||||||
|
"seed": 424242,
|
||||||
|
"settlement_count": 5,
|
||||||
|
"total_headcount": 52000
|
||||||
|
},
|
||||||
|
"full_economy_throughput_measured": false,
|
||||||
|
"host_label": "Apple_M1_Max_64_GB",
|
||||||
|
"platform": "macOS",
|
||||||
|
"processor_count": 10,
|
||||||
|
"rendered_throughput_measured": false,
|
||||||
|
"sample_count": 3,
|
||||||
|
"schema_version": 1,
|
||||||
|
"workload_id": "regional_records_scheduler_v1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
class_name RegionalScaleBenchmark
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# This workload measures primitive regional records and deterministic scheduler
|
||||||
|
# draining only. It does not load scenes or exercise the complete economy.
|
||||||
|
const SCHEMA_VERSION := 1
|
||||||
|
const WORKLOAD_ID := &"regional_records_scheduler_v1"
|
||||||
|
const WORLD_ID := &"regional_benchmark"
|
||||||
|
const FIXED_SEED := 424242
|
||||||
|
const LOCATION_COUNT := 5
|
||||||
|
const SETTLEMENT_COUNT := 5
|
||||||
|
const CARAVAN_COUNT := 20
|
||||||
|
const DETAILED_MEMBERS_PER_CARAVAN := 12
|
||||||
|
const NAMED_PERSON_COUNT := 2000
|
||||||
|
const COHORT_RESIDENT_COUNT := 50000
|
||||||
|
const MAX_ACTIVE_VISUALS := 40
|
||||||
|
const MAX_DUE_TICK := 256
|
||||||
|
const ROLE_IDS: Array[StringName] = [&"artisan", &"farmer", &"guard", &"merchant", &"scholar"]
|
||||||
|
|
||||||
|
|
||||||
|
func build_fixture(seed_value: int = FIXED_SEED) -> RegionalWorldState:
|
||||||
|
var locations: Array[LocationStateRecord] = []
|
||||||
|
var settlements: Array[SettlementStateRecord] = []
|
||||||
|
var routes: Array[RouteStateRecord] = []
|
||||||
|
var groups: Array[MobileGroupStateRecord] = []
|
||||||
|
var persons: Array[PersonStateRecord] = []
|
||||||
|
var cohorts: Array[PopulationCohortRecord] = []
|
||||||
|
var polities: Array[PolityStateRecord] = []
|
||||||
|
var relations: Array[DiplomaticRelationRecord] = []
|
||||||
|
|
||||||
|
for index in LOCATION_COUNT:
|
||||||
|
var location_id := _location_id(index)
|
||||||
|
var location := LocationStateRecord.create(
|
||||||
|
location_id,
|
||||||
|
WORLD_ID,
|
||||||
|
&"settlement",
|
||||||
|
"Regional Settlement %02d" % index,
|
||||||
|
_address(location_id, Vector3(float(index) * 100.0, 0.0, 0.0))
|
||||||
|
)
|
||||||
|
var settlement := SettlementStateRecord.create(
|
||||||
|
_settlement_id(index),
|
||||||
|
location_id,
|
||||||
|
"Regional Settlement %02d" % index,
|
||||||
|
_polity_id(index),
|
||||||
|
2,
|
||||||
|
_group_id(index),
|
||||||
|
0,
|
||||||
|
{&"food": 10000.0 + index * 100.0, &"wood": 5000.0 + index * 50.0}
|
||||||
|
)
|
||||||
|
var polity := PolityStateRecord.create(
|
||||||
|
_polity_id(index), "Regional Polity %02d" % index, location_id, [_settlement_id(index)]
|
||||||
|
)
|
||||||
|
if location == null or settlement == null or polity == null:
|
||||||
|
return null
|
||||||
|
locations.append(location)
|
||||||
|
settlements.append(settlement)
|
||||||
|
polities.append(polity)
|
||||||
|
|
||||||
|
for index in LOCATION_COUNT:
|
||||||
|
var route := RouteStateRecord.create(
|
||||||
|
_route_id(index),
|
||||||
|
_location_id(index),
|
||||||
|
_location_id((index + 1) % LOCATION_COUNT),
|
||||||
|
20 + index,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if route == null:
|
||||||
|
return null
|
||||||
|
routes.append(route)
|
||||||
|
|
||||||
|
for group_index in CARAVAN_COUNT:
|
||||||
|
var route_index := group_index % LOCATION_COUNT
|
||||||
|
var member_ids: Array[StringName] = []
|
||||||
|
for member_index in DETAILED_MEMBERS_PER_CARAVAN:
|
||||||
|
member_ids.append(_person_id(group_index * DETAILED_MEMBERS_PER_CARAVAN + member_index))
|
||||||
|
var departure_tick := group_index * 3
|
||||||
|
var cargo := {
|
||||||
|
&"food":
|
||||||
|
float(
|
||||||
|
KeyedRandom.range_int(seed_value, &"cargo_food", _group_id(group_index), 0, 50, 99)
|
||||||
|
),
|
||||||
|
&"herb":
|
||||||
|
float(
|
||||||
|
KeyedRandom.range_int(seed_value, &"cargo_herb", _group_id(group_index), 0, 10, 29)
|
||||||
|
),
|
||||||
|
&"wood":
|
||||||
|
float(
|
||||||
|
KeyedRandom.range_int(seed_value, &"cargo_wood", _group_id(group_index), 0, 20, 49)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
var group := MobileGroupStateRecord.create_on_route(
|
||||||
|
_group_id(group_index),
|
||||||
|
&"caravan",
|
||||||
|
"Regional Caravan %02d" % group_index,
|
||||||
|
_polity_id(route_index),
|
||||||
|
_route_id(route_index),
|
||||||
|
_location_id(route_index),
|
||||||
|
_location_id((route_index + 1) % LOCATION_COUNT),
|
||||||
|
departure_tick,
|
||||||
|
departure_tick + 20 + route_index,
|
||||||
|
member_ids,
|
||||||
|
{},
|
||||||
|
cargo,
|
||||||
|
500.0
|
||||||
|
)
|
||||||
|
if group == null:
|
||||||
|
return null
|
||||||
|
groups.append(group)
|
||||||
|
|
||||||
|
for person_index in NAMED_PERSON_COUNT:
|
||||||
|
var person: PersonStateRecord
|
||||||
|
if person_index < CARAVAN_COUNT * DETAILED_MEMBERS_PER_CARAVAN:
|
||||||
|
var group_index := person_index / DETAILED_MEMBERS_PER_CARAVAN
|
||||||
|
var home_index := group_index % LOCATION_COUNT
|
||||||
|
person = PersonStateRecord.create_in_group(
|
||||||
|
_person_id(person_index),
|
||||||
|
"Regional Person %04d" % person_index,
|
||||||
|
_role_id(seed_value, person_index),
|
||||||
|
_polity_id(home_index),
|
||||||
|
_location_id(home_index),
|
||||||
|
_group_id(group_index)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
var location_index := (
|
||||||
|
(person_index - CARAVAN_COUNT * DETAILED_MEMBERS_PER_CARAVAN) % LOCATION_COUNT
|
||||||
|
)
|
||||||
|
var location_id := _location_id(location_index)
|
||||||
|
person = PersonStateRecord.create_at_location(
|
||||||
|
_person_id(person_index),
|
||||||
|
"Regional Person %04d" % person_index,
|
||||||
|
_role_id(seed_value, person_index),
|
||||||
|
_polity_id(location_index),
|
||||||
|
location_id,
|
||||||
|
_address(
|
||||||
|
location_id,
|
||||||
|
Vector3(float(person_index % 64), 0.0, float((person_index / 64) % 64))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if person == null:
|
||||||
|
return null
|
||||||
|
persons.append(person)
|
||||||
|
|
||||||
|
var residents_per_location := COHORT_RESIDENT_COUNT / LOCATION_COUNT
|
||||||
|
for index in LOCATION_COUNT:
|
||||||
|
var cohort := PopulationCohortRecord.create_at_location(
|
||||||
|
_cohort_id(index),
|
||||||
|
&"aggregate_residents",
|
||||||
|
_polity_id(index),
|
||||||
|
residents_per_location,
|
||||||
|
_location_id(index)
|
||||||
|
)
|
||||||
|
if cohort == null:
|
||||||
|
return null
|
||||||
|
cohorts.append(cohort)
|
||||||
|
|
||||||
|
return RegionalWorldState.create(
|
||||||
|
WORLD_ID, locations, settlements, routes, groups, persons, cohorts, polities, relations
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func run(execution_budget: int = 8, seed_value: int = FIXED_SEED) -> Dictionary:
|
||||||
|
if execution_budget not in [1, 8, RegionalJobScheduler.UNLIMITED_BUDGET]:
|
||||||
|
return {}
|
||||||
|
var total_started_usec := Time.get_ticks_usec()
|
||||||
|
var fixture_started_usec := Time.get_ticks_usec()
|
||||||
|
var state := build_fixture(seed_value)
|
||||||
|
var fixture_build_usec := maxi(Time.get_ticks_usec() - fixture_started_usec, 1)
|
||||||
|
if state == null:
|
||||||
|
return {}
|
||||||
|
var fixture_audit := audit_fixture(state)
|
||||||
|
if not fixture_audit["valid"]:
|
||||||
|
return {}
|
||||||
|
var state_dictionary := state.to_dictionary()
|
||||||
|
var state_json := JSON.stringify(state_dictionary)
|
||||||
|
var state_bytes := state_json.to_utf8_buffer().size()
|
||||||
|
var fixture_checksum := state.checksum()
|
||||||
|
|
||||||
|
var roundtrip_started_usec := Time.get_ticks_usec()
|
||||||
|
var restored := RegionalWorldState.from_dictionary(state_dictionary)
|
||||||
|
var roundtrip_usec := maxi(Time.get_ticks_usec() - roundtrip_started_usec, 1)
|
||||||
|
if (
|
||||||
|
restored == null
|
||||||
|
or restored.to_dictionary() != state_dictionary
|
||||||
|
or restored.checksum() != fixture_checksum
|
||||||
|
):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
var scheduler_started_usec := Time.get_ticks_usec()
|
||||||
|
var scheduler := _build_scheduler(state, seed_value)
|
||||||
|
var scheduler_build_usec := maxi(Time.get_ticks_usec() - scheduler_started_usec, 1)
|
||||||
|
if scheduler == null:
|
||||||
|
return {}
|
||||||
|
var initial_jobs := scheduler.size()
|
||||||
|
var scheduler_state_bytes := JSON.stringify(scheduler.to_dictionary()).to_utf8_buffer().size()
|
||||||
|
var execution_order: Array[StringName] = []
|
||||||
|
var processed_named_people := 0
|
||||||
|
var processed_cohort_residents := 0
|
||||||
|
var processed_cargo := 0.0
|
||||||
|
var backlog_peak := scheduler.size()
|
||||||
|
var drain_batches := 0
|
||||||
|
var max_batch_size := 0
|
||||||
|
var execution_started_usec := Time.get_ticks_usec()
|
||||||
|
while not scheduler.is_empty():
|
||||||
|
backlog_peak = maxi(backlog_peak, scheduler.size())
|
||||||
|
var batch := scheduler.take_due(MAX_DUE_TICK, execution_budget)
|
||||||
|
if batch.is_empty():
|
||||||
|
return {}
|
||||||
|
drain_batches += 1
|
||||||
|
max_batch_size = maxi(max_batch_size, batch.size())
|
||||||
|
for job in batch:
|
||||||
|
execution_order.append(job.get_job_id())
|
||||||
|
var payload := job.get_payload()
|
||||||
|
processed_named_people += int(payload.get("named_people", 0))
|
||||||
|
processed_cohort_residents += int(payload.get("cohort_residents", 0))
|
||||||
|
processed_cargo += float(payload.get("cargo_total", 0.0))
|
||||||
|
var execution_usec := maxi(Time.get_ticks_usec() - execution_started_usec, 1)
|
||||||
|
var final_audit := audit_fixture(state)
|
||||||
|
var headcount_conserved: bool = (
|
||||||
|
final_audit["named_person_count"] == fixture_audit["named_person_count"]
|
||||||
|
and final_audit["cohort_resident_count"] == fixture_audit["cohort_resident_count"]
|
||||||
|
and processed_named_people == NAMED_PERSON_COUNT
|
||||||
|
and processed_cohort_residents == COHORT_RESIDENT_COUNT
|
||||||
|
)
|
||||||
|
var cargo_conserved: bool = (
|
||||||
|
is_equal_approx(float(final_audit["cargo_total"]), float(fixture_audit["cargo_total"]))
|
||||||
|
and is_equal_approx(processed_cargo, float(fixture_audit["cargo_total"]))
|
||||||
|
)
|
||||||
|
var order_checksum := JSON.stringify(execution_order).sha256_text()
|
||||||
|
var final_checksum := (
|
||||||
|
JSON
|
||||||
|
. stringify(
|
||||||
|
{
|
||||||
|
"fixture_checksum": fixture_checksum,
|
||||||
|
"execution_order": execution_order,
|
||||||
|
"processed_cargo": processed_cargo,
|
||||||
|
"processed_cohort_residents": processed_cohort_residents,
|
||||||
|
"processed_named_people": processed_named_people,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
. sha256_text()
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"schema_version": SCHEMA_VERSION,
|
||||||
|
"workload_id": String(WORKLOAD_ID),
|
||||||
|
"benchmark_scope": "regional_data_and_scheduler_only",
|
||||||
|
"rendered_throughput_measured": false,
|
||||||
|
"full_economy_throughput_measured": false,
|
||||||
|
"seed": seed_value,
|
||||||
|
"location_count": LOCATION_COUNT,
|
||||||
|
"settlement_count": SETTLEMENT_COUNT,
|
||||||
|
"caravan_count": CARAVAN_COUNT,
|
||||||
|
"detailed_members_per_caravan": DETAILED_MEMBERS_PER_CARAVAN,
|
||||||
|
"detailed_caravan_member_count": fixture_audit["detailed_caravan_member_count"],
|
||||||
|
"named_person_count": fixture_audit["named_person_count"],
|
||||||
|
"cohort_resident_count": fixture_audit["cohort_resident_count"],
|
||||||
|
"total_headcount": fixture_audit["total_headcount"],
|
||||||
|
"max_active_visuals": MAX_ACTIVE_VISUALS,
|
||||||
|
"fixture_valid": fixture_audit["valid"],
|
||||||
|
"roundtrip_valid": true,
|
||||||
|
"headcount_conserved": headcount_conserved,
|
||||||
|
"cargo_conserved": cargo_conserved,
|
||||||
|
"cargo_total": fixture_audit["cargo_total"],
|
||||||
|
"processed_cargo_total": processed_cargo,
|
||||||
|
"fixture_checksum": fixture_checksum,
|
||||||
|
"execution_order_checksum": order_checksum,
|
||||||
|
"final_checksum": final_checksum,
|
||||||
|
"execution_order": execution_order,
|
||||||
|
"initial_job_count": initial_jobs,
|
||||||
|
"executed_job_count": execution_order.size(),
|
||||||
|
"execution_budget": execution_budget,
|
||||||
|
"backlog_initial": initial_jobs,
|
||||||
|
"backlog_peak": backlog_peak,
|
||||||
|
"backlog_final": scheduler.size(),
|
||||||
|
"drain_batches": drain_batches,
|
||||||
|
"max_batch_size": max_batch_size,
|
||||||
|
"regional_state_bytes": state_bytes,
|
||||||
|
"scheduler_state_bytes": scheduler_state_bytes,
|
||||||
|
"fixture_build_usec": fixture_build_usec,
|
||||||
|
"roundtrip_usec": roundtrip_usec,
|
||||||
|
"scheduler_build_usec": scheduler_build_usec,
|
||||||
|
"execution_usec": execution_usec,
|
||||||
|
"elapsed_usec": maxi(Time.get_ticks_usec() - total_started_usec, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func audit_fixture(state: RegionalWorldState) -> Dictionary:
|
||||||
|
if state == null:
|
||||||
|
return {"valid": false}
|
||||||
|
var valid := (
|
||||||
|
state.get_world_id() == WORLD_ID
|
||||||
|
and state.get_all_locations().size() == LOCATION_COUNT
|
||||||
|
and state.get_all_settlements().size() == SETTLEMENT_COUNT
|
||||||
|
and state.get_all_routes().size() == LOCATION_COUNT
|
||||||
|
and state.get_all_mobile_groups().size() == CARAVAN_COUNT
|
||||||
|
and state.get_all_persons().size() == NAMED_PERSON_COUNT
|
||||||
|
and state.get_all_population_cohorts().size() == LOCATION_COUNT
|
||||||
|
and state.get_all_polities().size() == LOCATION_COUNT
|
||||||
|
)
|
||||||
|
var detailed_members := 0
|
||||||
|
var cargo_total := 0.0
|
||||||
|
for group in state.get_all_mobile_groups():
|
||||||
|
var member_ids := group.get_detailed_member_ids()
|
||||||
|
detailed_members += member_ids.size()
|
||||||
|
cargo_total += RegionalStateSupport.amount_ledger_total(group.get_cargo_ledger())
|
||||||
|
valid = (
|
||||||
|
valid
|
||||||
|
and member_ids.size() == DETAILED_MEMBERS_PER_CARAVAN
|
||||||
|
and (
|
||||||
|
state.get_persons_in_group(group.get_group_id()).size()
|
||||||
|
== DETAILED_MEMBERS_PER_CARAVAN
|
||||||
|
)
|
||||||
|
and group.is_on_route()
|
||||||
|
)
|
||||||
|
for person_id in member_ids:
|
||||||
|
valid = valid and state.get_entity_type(person_id) == &"person"
|
||||||
|
var cohort_residents := 0
|
||||||
|
for cohort in state.get_all_population_cohorts():
|
||||||
|
cohort_residents += cohort.get_count()
|
||||||
|
valid = valid and cohort.is_at_location()
|
||||||
|
for index in LOCATION_COUNT:
|
||||||
|
valid = (
|
||||||
|
valid
|
||||||
|
and state.get_settlements_at_location(_location_id(index)).size() == 1
|
||||||
|
and state.get_entity_type(_location_id(index)) == &"location"
|
||||||
|
and state.get_entity_type(_settlement_id(index)) == &"settlement"
|
||||||
|
and state.get_entity_type(_polity_id(index)) == &"polity"
|
||||||
|
)
|
||||||
|
valid = (
|
||||||
|
valid
|
||||||
|
and detailed_members == CARAVAN_COUNT * DETAILED_MEMBERS_PER_CARAVAN
|
||||||
|
and cohort_residents == COHORT_RESIDENT_COUNT
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"valid": valid,
|
||||||
|
"named_person_count": state.get_all_persons().size(),
|
||||||
|
"detailed_caravan_member_count": detailed_members,
|
||||||
|
"cohort_resident_count": cohort_residents,
|
||||||
|
"total_headcount": state.get_all_persons().size() + cohort_residents,
|
||||||
|
"cargo_total": cargo_total,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _build_scheduler(state: RegionalWorldState, seed_value: int) -> RegionalJobScheduler:
|
||||||
|
var scheduler := RegionalJobScheduler.new()
|
||||||
|
for person in state.get_all_persons():
|
||||||
|
if (
|
||||||
|
scheduler.schedule_new(
|
||||||
|
&"regional_person:%s" % person.get_person_id(),
|
||||||
|
&"regional_person_review",
|
||||||
|
KeyedRandom.range_int(
|
||||||
|
seed_value, &"regional_person_job", person.get_person_id(), 0, 1, MAX_DUE_TICK
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
person.get_person_id(),
|
||||||
|
{"named_people": 1}
|
||||||
|
)
|
||||||
|
== null
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
for cohort in state.get_all_population_cohorts():
|
||||||
|
if (
|
||||||
|
scheduler.schedule_new(
|
||||||
|
&"regional_cohort:%s" % cohort.get_cohort_id(),
|
||||||
|
&"regional_cohort_review",
|
||||||
|
KeyedRandom.range_int(
|
||||||
|
seed_value, &"regional_cohort_job", cohort.get_cohort_id(), 0, 1, MAX_DUE_TICK
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
cohort.get_cohort_id(),
|
||||||
|
{"cohort_residents": cohort.get_count()}
|
||||||
|
)
|
||||||
|
== null
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
for group in state.get_all_mobile_groups():
|
||||||
|
if (
|
||||||
|
scheduler.schedule_new(
|
||||||
|
&"regional_caravan:%s" % group.get_group_id(),
|
||||||
|
&"regional_caravan_review",
|
||||||
|
KeyedRandom.range_int(
|
||||||
|
seed_value, &"regional_caravan_job", group.get_group_id(), 0, 1, MAX_DUE_TICK
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
group.get_group_id(),
|
||||||
|
{"cargo_total": RegionalStateSupport.amount_ledger_total(group.get_cargo_ledger())}
|
||||||
|
)
|
||||||
|
== null
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
return scheduler
|
||||||
|
|
||||||
|
|
||||||
|
func _role_id(seed_value: int, person_index: int) -> StringName:
|
||||||
|
var role_index := KeyedRandom.range_int(
|
||||||
|
seed_value, &"person_role", _person_id(person_index), 0, 0, ROLE_IDS.size() - 1
|
||||||
|
)
|
||||||
|
return ROLE_IDS[role_index]
|
||||||
|
|
||||||
|
|
||||||
|
func _address(location_id: StringName, position: Vector3) -> SpatialAddress:
|
||||||
|
return SpatialAddress.create(WORLD_ID, location_id, position)
|
||||||
|
|
||||||
|
|
||||||
|
func _location_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_location_%02d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _settlement_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_settlement_%02d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _route_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_route_%02d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _group_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_caravan_%02d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _person_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_person_%04d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _cohort_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_residents_%02d" % index)
|
||||||
|
|
||||||
|
|
||||||
|
func _polity_id(index: int) -> StringName:
|
||||||
|
return StringName("regional_polity_%02d" % index)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bbh75scb18pak
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
extends GutTest
|
||||||
|
|
||||||
|
const BenchmarkScript := preload("res://simulation/benchmark/RegionalScaleBenchmark.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func test_exact_regional_workload_round_trips_and_conserves_membership() -> void:
|
||||||
|
var benchmark := BenchmarkScript.new()
|
||||||
|
var state := benchmark.build_fixture(BenchmarkScript.FIXED_SEED)
|
||||||
|
assert_not_null(state)
|
||||||
|
var audit := benchmark.audit_fixture(state)
|
||||||
|
assert_true(audit["valid"])
|
||||||
|
assert_eq(state.get_all_locations().size(), 5)
|
||||||
|
assert_eq(state.get_all_settlements().size(), 5)
|
||||||
|
assert_eq(state.get_all_mobile_groups().size(), 20)
|
||||||
|
assert_eq(audit["detailed_caravan_member_count"], 240)
|
||||||
|
assert_eq(audit["named_person_count"], 2000)
|
||||||
|
assert_eq(audit["cohort_resident_count"], 50000)
|
||||||
|
assert_eq(audit["total_headcount"], 52000)
|
||||||
|
for group in state.get_all_mobile_groups():
|
||||||
|
assert_eq(group.get_detailed_member_ids().size(), 12)
|
||||||
|
assert_eq(state.get_persons_in_group(group.get_group_id()).size(), 12)
|
||||||
|
var serialized := state.to_dictionary()
|
||||||
|
var restored := RegionalWorldState.from_dictionary(serialized)
|
||||||
|
assert_not_null(restored)
|
||||||
|
assert_eq(restored.to_dictionary(), serialized)
|
||||||
|
assert_eq(restored.checksum(), state.checksum())
|
||||||
|
assert_almost_eq(
|
||||||
|
float(benchmark.audit_fixture(restored)["cargo_total"]), float(audit["cargo_total"]), 0.0001
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func test_scheduler_budgets_and_three_repeats_preserve_order_and_checksum() -> void:
|
||||||
|
var benchmark := BenchmarkScript.new()
|
||||||
|
var budget_one := benchmark.run(1)
|
||||||
|
var budget_eight := benchmark.run(8)
|
||||||
|
var budget_eight_repeat_a := benchmark.run(8)
|
||||||
|
var budget_eight_repeat_b := benchmark.run(8)
|
||||||
|
var unlimited := benchmark.run(RegionalJobScheduler.UNLIMITED_BUDGET)
|
||||||
|
for result in [
|
||||||
|
budget_one, budget_eight, budget_eight_repeat_a, budget_eight_repeat_b, unlimited
|
||||||
|
]:
|
||||||
|
_assert_valid_result(result)
|
||||||
|
|
||||||
|
for result in [budget_one, unlimited, budget_eight_repeat_a, budget_eight_repeat_b]:
|
||||||
|
assert_eq(result["execution_order"], budget_eight["execution_order"])
|
||||||
|
assert_eq(result["execution_order_checksum"], budget_eight["execution_order_checksum"])
|
||||||
|
assert_eq(result["final_checksum"], budget_eight["final_checksum"])
|
||||||
|
assert_eq(result["fixture_checksum"], budget_eight["fixture_checksum"])
|
||||||
|
assert_eq(result["regional_state_bytes"], budget_eight["regional_state_bytes"])
|
||||||
|
assert_eq(budget_one["drain_batches"], budget_one["initial_job_count"])
|
||||||
|
assert_eq(budget_one["max_batch_size"], 1)
|
||||||
|
assert_eq(budget_eight["drain_batches"], ceili(float(budget_eight["initial_job_count"]) / 8.0))
|
||||||
|
assert_eq(budget_eight["max_batch_size"], 8)
|
||||||
|
assert_eq(unlimited["drain_batches"], 1)
|
||||||
|
assert_eq(unlimited["max_batch_size"], unlimited["initial_job_count"])
|
||||||
|
|
||||||
|
|
||||||
|
func _assert_valid_result(result: Dictionary) -> void:
|
||||||
|
assert_false(result.is_empty())
|
||||||
|
if result.is_empty():
|
||||||
|
return
|
||||||
|
assert_eq(result["schema_version"], 1)
|
||||||
|
assert_eq(StringName(result["workload_id"]), &"regional_records_scheduler_v1")
|
||||||
|
assert_eq(result["benchmark_scope"], "regional_data_and_scheduler_only")
|
||||||
|
assert_false(result["rendered_throughput_measured"])
|
||||||
|
assert_false(result["full_economy_throughput_measured"])
|
||||||
|
assert_eq(result["seed"], 424242)
|
||||||
|
assert_eq(result["location_count"], 5)
|
||||||
|
assert_eq(result["settlement_count"], 5)
|
||||||
|
assert_eq(result["caravan_count"], 20)
|
||||||
|
assert_eq(result["detailed_members_per_caravan"], 12)
|
||||||
|
assert_eq(result["detailed_caravan_member_count"], 240)
|
||||||
|
assert_eq(result["named_person_count"], 2000)
|
||||||
|
assert_eq(result["cohort_resident_count"], 50000)
|
||||||
|
assert_eq(result["total_headcount"], 52000)
|
||||||
|
assert_eq(result["max_active_visuals"], 40)
|
||||||
|
assert_true(result["fixture_valid"])
|
||||||
|
assert_true(result["roundtrip_valid"])
|
||||||
|
assert_true(result["headcount_conserved"])
|
||||||
|
assert_true(result["cargo_conserved"])
|
||||||
|
assert_eq(result["initial_job_count"], 2025)
|
||||||
|
assert_eq(result["executed_job_count"], 2025)
|
||||||
|
assert_eq(result["backlog_initial"], 2025)
|
||||||
|
assert_eq(result["backlog_peak"], 2025)
|
||||||
|
assert_eq(result["backlog_final"], 0)
|
||||||
|
assert_gt(result["regional_state_bytes"], 0)
|
||||||
|
assert_gt(result["scheduler_state_bytes"], 0)
|
||||||
|
for timing_key in [
|
||||||
|
"fixture_build_usec",
|
||||||
|
"roundtrip_usec",
|
||||||
|
"scheduler_build_usec",
|
||||||
|
"execution_usec",
|
||||||
|
"elapsed_usec",
|
||||||
|
]:
|
||||||
|
assert_gt(result[timing_key], 0)
|
||||||
|
assert_eq(String(result["fixture_checksum"]).length(), 64)
|
||||||
|
assert_eq(String(result["execution_order_checksum"]).length(), 64)
|
||||||
|
assert_eq(String(result["final_checksum"]).length(), 64)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://crgxogf0ldlki
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const BenchmarkScript := preload("res://simulation/benchmark/RegionalScaleBenchmark.gd")
|
||||||
|
const DEFAULT_OUTPUT_PATH := "user://regional_scale_latest.json"
|
||||||
|
const DEFAULT_HOST_LABEL := "unspecified"
|
||||||
|
const SAMPLE_COUNT := 3
|
||||||
|
const DEFAULT_BUDGET := 8
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var benchmark := BenchmarkScript.new()
|
||||||
|
var samples: Array[Dictionary] = []
|
||||||
|
for _sample_index in SAMPLE_COUNT:
|
||||||
|
var sample := benchmark.run(DEFAULT_BUDGET)
|
||||||
|
if not _valid_sample(sample):
|
||||||
|
push_error("Regional scale benchmark produced an invalid default-budget sample")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
samples.append(sample)
|
||||||
|
var budget_one := benchmark.run(1)
|
||||||
|
var unlimited := benchmark.run(RegionalJobScheduler.UNLIMITED_BUDGET)
|
||||||
|
if not _valid_sample(budget_one) or not _valid_sample(unlimited):
|
||||||
|
push_error("Regional scale benchmark produced an invalid budget-parity sample")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
if not _deterministic_samples(samples + [budget_one, unlimited]):
|
||||||
|
push_error("Regional scale benchmark diverged across samples or execution budgets")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var report := {
|
||||||
|
"schema_version": BenchmarkScript.SCHEMA_VERSION,
|
||||||
|
"captured_utc": Time.get_datetime_string_from_system(true) + "Z",
|
||||||
|
"engine_version": String(Engine.get_version_info().get("string", "unknown")),
|
||||||
|
"platform": OS.get_name(),
|
||||||
|
"processor_count": OS.get_processor_count(),
|
||||||
|
"host_label": _get_argument_value("--host-label=", DEFAULT_HOST_LABEL),
|
||||||
|
"workload_id": String(BenchmarkScript.WORKLOAD_ID),
|
||||||
|
"benchmark_scope": "regional_data_and_scheduler_only",
|
||||||
|
"rendered_throughput_measured": false,
|
||||||
|
"full_economy_throughput_measured": false,
|
||||||
|
"sample_count": SAMPLE_COUNT,
|
||||||
|
"default_execution_budget": DEFAULT_BUDGET,
|
||||||
|
"fixture": _fixture_summary(samples[0]),
|
||||||
|
"default_budget": _summarize_timings(samples),
|
||||||
|
"budget_parity": {
|
||||||
|
"budget_one_final_checksum": budget_one["final_checksum"],
|
||||||
|
"budget_one_drain_batches": budget_one["drain_batches"],
|
||||||
|
"unlimited_final_checksum": unlimited["final_checksum"],
|
||||||
|
"unlimited_drain_batches": unlimited["drain_batches"],
|
||||||
|
"identical": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var output_path := _get_argument_value("--output=", DEFAULT_OUTPUT_PATH)
|
||||||
|
var output := FileAccess.open(output_path, FileAccess.WRITE)
|
||||||
|
if output == null:
|
||||||
|
push_error("Could not write regional scale benchmark report to %s" % output_path)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
output.store_string(JSON.stringify(report, "\t") + "\n")
|
||||||
|
output.close()
|
||||||
|
print(
|
||||||
|
(
|
||||||
|
"[BENCH] regional | %d named + %d aggregate | %d caravans | "
|
||||||
|
+ "%d jobs | median %.2f ms total | %.2f MiB state"
|
||||||
|
)
|
||||||
|
% [
|
||||||
|
report["fixture"]["named_person_count"],
|
||||||
|
report["fixture"]["cohort_resident_count"],
|
||||||
|
report["fixture"]["caravan_count"],
|
||||||
|
report["fixture"]["initial_job_count"],
|
||||||
|
float(report["default_budget"]["elapsed_usec_median"]) / 1000.0,
|
||||||
|
float(report["fixture"]["regional_state_bytes"]) / (1024.0 * 1024.0),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
print("[BENCH] Report: %s" % ProjectSettings.globalize_path(output_path))
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _valid_sample(sample: Dictionary) -> bool:
|
||||||
|
return (
|
||||||
|
not sample.is_empty()
|
||||||
|
and bool(sample.get("fixture_valid", false))
|
||||||
|
and bool(sample.get("roundtrip_valid", false))
|
||||||
|
and bool(sample.get("headcount_conserved", false))
|
||||||
|
and bool(sample.get("cargo_conserved", false))
|
||||||
|
and int(sample.get("backlog_final", -1)) == 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _deterministic_samples(samples: Array) -> bool:
|
||||||
|
if samples.is_empty():
|
||||||
|
return false
|
||||||
|
var expected: Dictionary = samples[0]
|
||||||
|
for sample: Dictionary in samples:
|
||||||
|
for key in [
|
||||||
|
"fixture_checksum",
|
||||||
|
"execution_order_checksum",
|
||||||
|
"final_checksum",
|
||||||
|
"initial_job_count",
|
||||||
|
"executed_job_count",
|
||||||
|
"regional_state_bytes",
|
||||||
|
"scheduler_state_bytes",
|
||||||
|
]:
|
||||||
|
if sample.get(key) != expected.get(key):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _fixture_summary(sample: Dictionary) -> Dictionary:
|
||||||
|
var result := {}
|
||||||
|
for key in [
|
||||||
|
"seed",
|
||||||
|
"location_count",
|
||||||
|
"settlement_count",
|
||||||
|
"caravan_count",
|
||||||
|
"detailed_members_per_caravan",
|
||||||
|
"detailed_caravan_member_count",
|
||||||
|
"named_person_count",
|
||||||
|
"cohort_resident_count",
|
||||||
|
"total_headcount",
|
||||||
|
"max_active_visuals",
|
||||||
|
"cargo_total",
|
||||||
|
"initial_job_count",
|
||||||
|
"regional_state_bytes",
|
||||||
|
"scheduler_state_bytes",
|
||||||
|
"fixture_checksum",
|
||||||
|
"execution_order_checksum",
|
||||||
|
"final_checksum",
|
||||||
|
]:
|
||||||
|
result[key] = sample[key]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _summarize_timings(samples: Array[Dictionary]) -> Dictionary:
|
||||||
|
var result := {}
|
||||||
|
for key in [
|
||||||
|
"fixture_build_usec",
|
||||||
|
"roundtrip_usec",
|
||||||
|
"scheduler_build_usec",
|
||||||
|
"execution_usec",
|
||||||
|
"elapsed_usec",
|
||||||
|
]:
|
||||||
|
var values: Array[int] = []
|
||||||
|
for sample in samples:
|
||||||
|
values.append(int(sample[key]))
|
||||||
|
values.sort()
|
||||||
|
result[key + "_samples"] = values
|
||||||
|
result[key + "_median"] = values[values.size() / 2]
|
||||||
|
result["drain_batches"] = samples[0]["drain_batches"]
|
||||||
|
result["backlog_initial"] = samples[0]["backlog_initial"]
|
||||||
|
result["backlog_peak"] = samples[0]["backlog_peak"]
|
||||||
|
result["backlog_final"] = samples[0]["backlog_final"]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _get_argument_value(prefix: String, default_value: String) -> String:
|
||||||
|
for argument in OS.get_cmdline_user_args():
|
||||||
|
if argument.begins_with(prefix):
|
||||||
|
var value := argument.trim_prefix(prefix)
|
||||||
|
if not value.is_empty():
|
||||||
|
return value
|
||||||
|
return default_value
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://br2cgdiw4s6an
|
||||||
Reference in New Issue
Block a user