02c9843e1b
- Implemented runtime-only adaptive quality controller to optimize simulation performance based on CPU time. - Added adaptive quality thresholds and recovery cycles to dynamically adjust simulation fidelity. - Introduced single-step solving method for civilization transitions to improve performance under high loads. - Updated benchmark scripts to include speed parameter for testing various simulation speeds. - Added tests for adaptive quality adjustments and their effects on civilization flow calculations. - Documented results of optimization attempts and their impact on performance metrics.
68 lines
3.3 KiB
GDScript
68 lines
3.3 KiB
GDScript
extends SceneTree
|
|
## Full-cycle throughput benchmark; diagnostic input is never modified.
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
|
|
func _initialize():
|
|
call_deferred("run")
|
|
|
|
func restore_json_integers(value):
|
|
# JSON parses ordinary integer tokens as float. Large integers are $exact.
|
|
if value is float and value == floorf(value) and absf(value) <= 9007199254740991:
|
|
return int(value)
|
|
if value is Dictionary:
|
|
for key in value: value[key] = restore_json_integers(value[key])
|
|
elif value is Array:
|
|
for i in value.size(): value[i] = restore_json_integers(value[i])
|
|
return value
|
|
|
|
func run():
|
|
var args = OS.get_cmdline_user_args()
|
|
if args.is_empty():
|
|
print("Usage: --script tests/benchmark_civilization_flows.gd -- /path/to/save.adventure.json [delta_seconds] [profile] [cycles=100] [speed=1..6]")
|
|
quit(1)
|
|
return
|
|
var data = JSON.parse_string(FileAccess.get_file_as_string(args[0]))
|
|
if not data is Dictionary or data.get("format_version") != 6:
|
|
push_error("Expected diagnostic JSON format 6")
|
|
quit(1)
|
|
return
|
|
var initial: Dictionary = E.decode(restore_json_integers(data)).state
|
|
var validation: String = root.get_node("SaveManager").validate_data(E.decode(restore_json_integers(data)))
|
|
print("Save validation: ", "OK" if validation.is_empty() else validation)
|
|
var delta: float = args[1].to_float() if args.size() > 1 else 1.0 / 60.0
|
|
if not is_finite(delta) or delta <= 0:
|
|
quit(1)
|
|
return
|
|
var selected_speed: int = 6
|
|
for arg in args:
|
|
if arg.begins_with("speed="):
|
|
selected_speed = clampi(arg.trim_prefix("speed=").to_int(), 1, root.get_node("Global").SPEED_LIMIT)
|
|
for speed in [selected_speed]:
|
|
var world = load("res://world/owniverse/owniverse.gd").new()
|
|
world.catalog.load_definitions(root.get_node("Global").OwniverseEntities)
|
|
world.state = initial.duplicate(true)
|
|
world.speed = speed
|
|
world.profile_enabled = args.has("profile")
|
|
var elapsed: Array = []
|
|
var cycles: int = 100
|
|
for arg in args:
|
|
if arg.begins_with("cycles="): cycles = maxi(1, arg.trim_prefix("cycles=").to_int())
|
|
var initial_clock: Variant = world.state.time.datetime
|
|
for cycle in cycles:
|
|
var start: int = Time.get_ticks_usec()
|
|
world.evolute(delta)
|
|
elapsed.append((Time.get_ticks_usec() - start) / 1000.0)
|
|
print("Speed %d, %d complete %.3f-second steps" % [speed, cycles, delta])
|
|
print("Min/max ms: ", elapsed.min(), "/", elapsed.max())
|
|
print("Game years applied: ", E.fixed(E.subtract(world.state.time.datetime, initial_clock)))
|
|
var total_ms: float = 0.0
|
|
for sample in elapsed: total_ms += sample
|
|
var throughput: float = float(cycles) * delta / (total_ms / 1000.0)
|
|
print("Mean complete cycle ms: ", total_ms / cycles)
|
|
print("Full cycles/s: ", float(cycles) * 1000.0 / total_ms)
|
|
print("Simulation throughput: %.3f real-time seconds processed per CPU second; needs >= 1.0 to keep up" % throughput)
|
|
if world.profile_enabled: print("CPU profile (usec, calls): ", world.profile)
|
|
for entity in world.catalog.entities.values(): entity.free()
|
|
world.free()
|
|
quit()
|