Phase 5.2: performance benchmark suite for grid/entity/FOV/pathfinding
Adds 6 benchmark scripts in tests/benchmarks/ covering all 5 scenarios from Kanboard #37, plus a shared baseline helper: grid_step_bench.py 100 ent / 100x100 grid / 1000 grid.step() rounds mix of IDLE/NOISE8/SEEK/FLEE behaviors fov_opt_bench.py 100 ent / 1000x1000 grid; entity.update_visibility() (with DiscreteMap perspective writeback) vs bare grid.compute_fov() (no writeback) across FOV algorithms BASIC/SHADOW/SYMMETRIC_SHADOWCAST and radii 8/16/32 spatial_hash_bench.py entities_in_radius() at radii (1,5,10,50) x entity counts (100,1k,10k); compares against naive O(n) baseline with hit-count validation pathfinding_bench.py A* across grid sizes/densities/heuristics/weights, plus with-vs-without `collide=` collision-label comparison (0/10/100 blockers on 100x100) gridview_render_bench.py 1/2/4 GridViews on shared grid; uses automation.screenshot() to force real renders in headless mode (mcrfpy.step alone is render-stubbed) dijkstra_bench.py single-root, multi-root, mask, invert, descent _baseline.py writes baseline JSON to baseline/phase5_2/ All scripts emit JSON to stdout and write a baseline copy under tests/benchmarks/baseline/phase5_2/ for regression comparison. All run headless; pure time.perf_counter() timing for compute benches, screenshot wall-time for the render bench (start/end_benchmark would only capture the no-op headless game loop, so direct timing is used). Notable findings captured in baselines: - spatial hash: 5x to >300x speedup over naive O(n), hits validated identical - update_visibility: ~25-37 ms/entity perspective writeback overhead on 1000x1000 grid (full-grid demote+promote loop in UIEntity::updateVisibility) dominates over the actual TCOD FOV cost (~3-24 ms). Worth a follow-up issue for sparse perspective updating. - gridview render: per-view cost scales near-linearly down (~78ms total for 1, 2, or 4 views) -- the multi-view system shares state efficiently. Refs Kanboard #37.
This commit is contained in:
parent
98a9497a6c
commit
59e722166a
13 changed files with 2215 additions and 0 deletions
18
tests/benchmarks/_baseline.py
Normal file
18
tests/benchmarks/_baseline.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"""Helper for Phase 5.2 benchmark scripts to write JSON baselines.
|
||||
|
||||
Each bench script calls `_baseline.write("name.json", out_dict)` to save its
|
||||
results to `tests/benchmarks/baseline/phase5_2/<name>`. Future runs can be
|
||||
diffed against these for regression detection.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def write(filename, payload):
|
||||
base = os.path.join(os.path.dirname(__file__), "baseline", "phase5_2")
|
||||
os.makedirs(base, exist_ok=True)
|
||||
path = os.path.join(base, filename)
|
||||
with open(path, "w") as f:
|
||||
json.dump(payload, f, indent=2)
|
||||
print(f" baseline written: {path}")
|
||||
return path
|
||||
86
tests/benchmarks/baseline/phase5_2/dijkstra_bench.json
Normal file
86
tests/benchmarks/baseline/phase5_2/dijkstra_bench.json
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"runs": [
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "multi_root",
|
||||
"roots": 1,
|
||||
"mean_ms": 0.7709094556048512
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "multi_root",
|
||||
"roots": 2,
|
||||
"mean_ms": 0.7632468361407518
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "multi_root",
|
||||
"roots": 5,
|
||||
"mean_ms": 1.200081780552864
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "multi_root",
|
||||
"roots": 20,
|
||||
"mean_ms": 2.137616788968444
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "mask",
|
||||
"roots": 500,
|
||||
"mean_ms": 30.424197972752154
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "invert",
|
||||
"mean_ms": 1.0323396185413003
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"kind": "descent_step_per_call",
|
||||
"mean_us": 0.4075700417160988,
|
||||
"valid_per_trial": 100
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "multi_root",
|
||||
"roots": 1,
|
||||
"mean_ms": 26.075413217768073
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "multi_root",
|
||||
"roots": 2,
|
||||
"mean_ms": 25.83242394030094
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "multi_root",
|
||||
"roots": 5,
|
||||
"mean_ms": 33.73005616012961
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "multi_root",
|
||||
"roots": 20,
|
||||
"mean_ms": 78.58918677084148
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "mask",
|
||||
"roots": 12500,
|
||||
"mean_ms": 18658.679087948985
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "invert",
|
||||
"mean_ms": 25.918347598053515
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"kind": "descent_step_per_call",
|
||||
"mean_us": 0.3717193566262722,
|
||||
"valid_per_trial": 2500
|
||||
}
|
||||
]
|
||||
}
|
||||
120
tests/benchmarks/baseline/phase5_2/fov_opt_bench.json
Normal file
120
tests/benchmarks/baseline/phase5_2/fov_opt_bench.json
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"config": {
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"radii": [
|
||||
8,
|
||||
16,
|
||||
32
|
||||
],
|
||||
"algorithms": [
|
||||
"BASIC",
|
||||
"SHADOW",
|
||||
"SYMMETRIC_SHADOWCAST"
|
||||
],
|
||||
"warmup_rounds": 1,
|
||||
"measured_rounds": 3,
|
||||
"seed": 6699
|
||||
},
|
||||
"runs": [
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "BASIC",
|
||||
"radius": 8,
|
||||
"with_perspective_round_ms": 2769.56388001175,
|
||||
"without_perspective_round_ms": 271.07496932148933,
|
||||
"with_perspective_per_entity_us": 27695.638800117496,
|
||||
"without_perspective_per_entity_us": 2710.7496932148933,
|
||||
"perspective_overhead_per_entity_us": 24984.889106902603
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "BASIC",
|
||||
"radius": 16,
|
||||
"with_perspective_round_ms": 2984.1214296563217,
|
||||
"without_perspective_round_ms": 279.2910839974259,
|
||||
"with_perspective_per_entity_us": 29841.214296563216,
|
||||
"without_perspective_per_entity_us": 2792.910839974259,
|
||||
"perspective_overhead_per_entity_us": 27048.303456588957
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "BASIC",
|
||||
"radius": 32,
|
||||
"with_perspective_round_ms": 2893.4406669965633,
|
||||
"without_perspective_round_ms": 295.72028065255535,
|
||||
"with_perspective_per_entity_us": 28934.406669965636,
|
||||
"without_perspective_per_entity_us": 2957.202806525553,
|
||||
"perspective_overhead_per_entity_us": 25977.20386344008
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SHADOW",
|
||||
"radius": 8,
|
||||
"with_perspective_round_ms": 2910.704257975643,
|
||||
"without_perspective_round_ms": 287.07114366504055,
|
||||
"with_perspective_per_entity_us": 29107.04257975643,
|
||||
"without_perspective_per_entity_us": 2870.7114366504056,
|
||||
"perspective_overhead_per_entity_us": 26236.331143106025
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SHADOW",
|
||||
"radius": 16,
|
||||
"with_perspective_round_ms": 3024.507179002588,
|
||||
"without_perspective_round_ms": 288.10382665445405,
|
||||
"with_perspective_per_entity_us": 30245.07179002588,
|
||||
"without_perspective_per_entity_us": 2881.0382665445404,
|
||||
"perspective_overhead_per_entity_us": 27364.03352348134
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SHADOW",
|
||||
"radius": 32,
|
||||
"with_perspective_round_ms": 2899.84968265829,
|
||||
"without_perspective_round_ms": 284.8557453447332,
|
||||
"with_perspective_per_entity_us": 28998.4968265829,
|
||||
"without_perspective_per_entity_us": 2848.5574534473317,
|
||||
"perspective_overhead_per_entity_us": 26149.93937313557
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SYMMETRIC_SHADOWCAST",
|
||||
"radius": 8,
|
||||
"with_perspective_round_ms": 4872.0670250089215,
|
||||
"without_perspective_round_ms": 2398.7115593239046,
|
||||
"with_perspective_per_entity_us": 48720.67025008921,
|
||||
"without_perspective_per_entity_us": 23987.115593239043,
|
||||
"perspective_overhead_per_entity_us": 24733.55465685017
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SYMMETRIC_SHADOWCAST",
|
||||
"radius": 16,
|
||||
"with_perspective_round_ms": 5419.6644353602705,
|
||||
"without_perspective_round_ms": 2340.30764332662,
|
||||
"with_perspective_per_entity_us": 54196.64435360271,
|
||||
"without_perspective_per_entity_us": 23403.076433266204,
|
||||
"perspective_overhead_per_entity_us": 30793.567920336503
|
||||
},
|
||||
{
|
||||
"grid": "1000x1000",
|
||||
"entities": 100,
|
||||
"algorithm": "SYMMETRIC_SHADOWCAST",
|
||||
"radius": 32,
|
||||
"with_perspective_round_ms": 6089.957528670008,
|
||||
"without_perspective_round_ms": 2441.893618670292,
|
||||
"with_perspective_per_entity_us": 60899.575286700085,
|
||||
"without_perspective_per_entity_us": 24418.936186702922,
|
||||
"perspective_overhead_per_entity_us": 36480.63909999716
|
||||
}
|
||||
]
|
||||
}
|
||||
9
tests/benchmarks/baseline/phase5_2/grid_step_bench.json
Normal file
9
tests/benchmarks/baseline/phase5_2/grid_step_bench.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"grid": "100x100",
|
||||
"entities": 100,
|
||||
"rounds": 1000,
|
||||
"total_sec": 0.07824495097156614,
|
||||
"mean_round_ms": 0.07824495097156614,
|
||||
"p95_round_ms": 0.1227830071002245,
|
||||
"per_entity_step_us": 0.7824495097156614
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"runs": [
|
||||
{
|
||||
"views": 1,
|
||||
"frames": 60,
|
||||
"warmup_frames": 5,
|
||||
"total_sec": 4.710599604761228,
|
||||
"mean_frame_ms": 78.50999341268714,
|
||||
"p95_frame_ms": 90.47448402270675,
|
||||
"implied_fps": 12.737231994702995,
|
||||
"per_view_frame_ms": 78.50999341268714
|
||||
},
|
||||
{
|
||||
"views": 2,
|
||||
"frames": 60,
|
||||
"warmup_frames": 5,
|
||||
"total_sec": 4.6525509969796985,
|
||||
"mean_frame_ms": 77.54251661632831,
|
||||
"p95_frame_ms": 91.92000096663833,
|
||||
"implied_fps": 12.896150958678424,
|
||||
"per_view_frame_ms": 38.77125830816416
|
||||
},
|
||||
{
|
||||
"views": 4,
|
||||
"frames": 60,
|
||||
"warmup_frames": 5,
|
||||
"total_sec": 4.727940998389386,
|
||||
"mean_frame_ms": 78.7990166398231,
|
||||
"p95_frame_ms": 99.88687501754612,
|
||||
"implied_fps": 12.690513697281654,
|
||||
"per_view_frame_ms": 19.699754159955774
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"grid": "80x80",
|
||||
"frames": 60,
|
||||
"warmup_frames": 5,
|
||||
"view_counts": [
|
||||
1,
|
||||
2,
|
||||
4
|
||||
],
|
||||
"view_pixel_size": [
|
||||
320,
|
||||
320
|
||||
]
|
||||
}
|
||||
}
|
||||
969
tests/benchmarks/baseline/phase5_2/pathfinding_bench.json
Normal file
969
tests/benchmarks/baseline/phase5_2/pathfinding_bench.json
Normal file
|
|
@ -0,0 +1,969 @@
|
|||
{
|
||||
"config": {
|
||||
"grid_sizes": [
|
||||
[
|
||||
100,
|
||||
100
|
||||
],
|
||||
[
|
||||
500,
|
||||
500
|
||||
]
|
||||
],
|
||||
"obstacle_densities": [
|
||||
0.1,
|
||||
0.3,
|
||||
0.5
|
||||
],
|
||||
"heuristics": [
|
||||
"EUCLIDEAN",
|
||||
"MANHATTAN",
|
||||
"CHEBYSHEV",
|
||||
"DIAGONAL",
|
||||
"ZERO"
|
||||
],
|
||||
"weights": [
|
||||
1.0,
|
||||
1.5,
|
||||
2.0
|
||||
],
|
||||
"trials": 5,
|
||||
"collide_blocker_counts": [
|
||||
0,
|
||||
10,
|
||||
100
|
||||
],
|
||||
"collide_trials": 20
|
||||
},
|
||||
"runs": [
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.942350598052144,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.08371900767087936,
|
||||
"hits": 5,
|
||||
"mean_length": 104.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.13862219639122486,
|
||||
"hits": 5,
|
||||
"mean_length": 104.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.09275858756154776,
|
||||
"hits": 5,
|
||||
"mean_length": 106.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.09280364029109478,
|
||||
"hits": 5,
|
||||
"mean_length": 106.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.08861918468028307,
|
||||
"hits": 5,
|
||||
"mean_length": 106.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.3062639636918902,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.09980038739740849,
|
||||
"hits": 5,
|
||||
"mean_length": 105.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.07879282347857952,
|
||||
"hits": 5,
|
||||
"mean_length": 107.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.3678584238514304,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.08070121984928846,
|
||||
"hits": 5,
|
||||
"mean_length": 104.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.07753577083349228,
|
||||
"hits": 5,
|
||||
"mean_length": 104.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.790003828704357,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 2.8951774118468165,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.86939381621778,
|
||||
"hits": 5,
|
||||
"mean_length": 103.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.7874952163547277,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.10141241364181042,
|
||||
"hits": 5,
|
||||
"mean_length": 114.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.09933756664395332,
|
||||
"hits": 5,
|
||||
"mean_length": 116.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.1215972239151597,
|
||||
"hits": 5,
|
||||
"mean_length": 121.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.11956898961216211,
|
||||
"hits": 5,
|
||||
"mean_length": 131.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.11518399696797132,
|
||||
"hits": 5,
|
||||
"mean_length": 131.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.6034855972975492,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.1681813970208168,
|
||||
"hits": 5,
|
||||
"mean_length": 110.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.10669098701328039,
|
||||
"hits": 5,
|
||||
"mean_length": 115.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.40994961746037006,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.09926019702106714,
|
||||
"hits": 5,
|
||||
"mean_length": 113.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.10138300713151693,
|
||||
"hits": 5,
|
||||
"mean_length": 117.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.339287009090185,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 2.4245378095656633,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.389551419764757,
|
||||
"hits": 5,
|
||||
"mean_length": 108.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.602034829556942,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.3661741968244314,
|
||||
"hits": 5,
|
||||
"mean_length": 148.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.2342308172956109,
|
||||
"hits": 5,
|
||||
"mean_length": 149.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.2803839975968003,
|
||||
"hits": 5,
|
||||
"mean_length": 157.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.20836759358644485,
|
||||
"hits": 5,
|
||||
"mean_length": 168.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.20984318107366562,
|
||||
"hits": 5,
|
||||
"mean_length": 169.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.5648985747247934,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.0680590057745576,
|
||||
"hits": 5,
|
||||
"mean_length": 153.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.5100774113088846,
|
||||
"hits": 5,
|
||||
"mean_length": 159.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.2593558290973306,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 0.35627696197479963,
|
||||
"hits": 5,
|
||||
"mean_length": 156.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 0.22568658459931612,
|
||||
"hits": 5,
|
||||
"mean_length": 158.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.7558214021846652,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.7436427995562553,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.7293283948674798,
|
||||
"hits": 5,
|
||||
"mean_length": 143.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 32.0626940112561,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.5519145876169205,
|
||||
"hits": 5,
|
||||
"mean_length": 522.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.4397500082850456,
|
||||
"hits": 5,
|
||||
"mean_length": 522.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.591819617897272,
|
||||
"hits": 5,
|
||||
"mean_length": 530.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.6432177973911166,
|
||||
"hits": 5,
|
||||
"mean_length": 546.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.6989499796181917,
|
||||
"hits": 5,
|
||||
"mean_length": 546.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 156.1333344085142,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.5195768093690276,
|
||||
"hits": 5,
|
||||
"mean_length": 521.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.4803750207647681,
|
||||
"hits": 5,
|
||||
"mean_length": 526.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 15.02777342684567,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.6023054020479321,
|
||||
"hits": 5,
|
||||
"mean_length": 520.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.6157740028575063,
|
||||
"hits": 5,
|
||||
"mean_length": 520.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 97.6897893473506,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 96.43948636949062,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.1,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 107.46402416843921,
|
||||
"hits": 5,
|
||||
"mean_length": 513.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 57.68123983871192,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.858492591418326,
|
||||
"hits": 5,
|
||||
"mean_length": 588.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.757865771651268,
|
||||
"hits": 5,
|
||||
"mean_length": 595.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.8272274173796177,
|
||||
"hits": 5,
|
||||
"mean_length": 586.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.9388356013223529,
|
||||
"hits": 5,
|
||||
"mean_length": 625.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.9301387714222074,
|
||||
"hits": 5,
|
||||
"mean_length": 622.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 96.00871021393687,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 6.980397994630039,
|
||||
"hits": 5,
|
||||
"mean_length": 572.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.038404601626098,
|
||||
"hits": 5,
|
||||
"mean_length": 604.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 33.08099440764636,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 1.853386196307838,
|
||||
"hits": 5,
|
||||
"mean_length": 589.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 1.8388852244243026,
|
||||
"hits": 5,
|
||||
"mean_length": 593.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 88.17679616622627,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 92.76693619322032,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.3,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 82.64447257388383,
|
||||
"hits": 5,
|
||||
"mean_length": 557.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 59.1546967625618,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 2.7708559995517135,
|
||||
"hits": 5,
|
||||
"mean_length": 715.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "EUCLIDEAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 4.107674001716077,
|
||||
"hits": 5,
|
||||
"mean_length": 733.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.8352454071864486,
|
||||
"hits": 5,
|
||||
"mean_length": 710.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 3.3035014057531953,
|
||||
"hits": 5,
|
||||
"mean_length": 767.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "MANHATTAN",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 3.7329247687011957,
|
||||
"hits": 5,
|
||||
"mean_length": 757.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 61.771947774104774,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 21.38091274537146,
|
||||
"hits": 5,
|
||||
"mean_length": 688.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "CHEBYSHEV",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 3.3959574066102505,
|
||||
"hits": 5,
|
||||
"mean_length": 711.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 42.92491900268942,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 3.1225387938320637,
|
||||
"hits": 5,
|
||||
"mean_length": 710.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "DIAGONAL",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 2.8334501897916198,
|
||||
"hits": 5,
|
||||
"mean_length": 721.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.0,
|
||||
"collide": null,
|
||||
"mean_ms": 57.06864399835467,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 1.5,
|
||||
"collide": null,
|
||||
"mean_ms": 55.27959519531578,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
},
|
||||
{
|
||||
"grid": "500x500",
|
||||
"density": 0.5,
|
||||
"heuristic": "ZERO",
|
||||
"weight": 2.0,
|
||||
"collide": null,
|
||||
"mean_ms": 55.80121979583055,
|
||||
"hits": 5,
|
||||
"mean_length": 683.0
|
||||
}
|
||||
],
|
||||
"collide_runs": [
|
||||
{
|
||||
"grid": "100x100",
|
||||
"blockers": 0,
|
||||
"without_collide_ms": 0.655252585420385,
|
||||
"with_collide_ms": 0.6339186511468142,
|
||||
"without_collide_path_len": 101.0,
|
||||
"with_collide_path_len": 101.0,
|
||||
"overhead_ms": -0.021333934273570776
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"blockers": 10,
|
||||
"without_collide_ms": 0.6333888624794781,
|
||||
"with_collide_ms": 0.624053756473586,
|
||||
"without_collide_path_len": 101.0,
|
||||
"with_collide_path_len": 101.0,
|
||||
"overhead_ms": -0.009335106005892158
|
||||
},
|
||||
{
|
||||
"grid": "100x100",
|
||||
"blockers": 100,
|
||||
"without_collide_ms": 0.6174998939968646,
|
||||
"with_collide_ms": 0.8494114445056766,
|
||||
"without_collide_path_len": 101.0,
|
||||
"with_collide_path_len": 102.0,
|
||||
"overhead_ms": 0.23191155050881207
|
||||
}
|
||||
]
|
||||
}
|
||||
141
tests/benchmarks/baseline/phase5_2/spatial_hash_bench.json
Normal file
141
tests/benchmarks/baseline/phase5_2/spatial_hash_bench.json
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"config": {
|
||||
"grid": "200x200",
|
||||
"entity_counts": [
|
||||
100,
|
||||
1000,
|
||||
10000
|
||||
],
|
||||
"radii": [
|
||||
1,
|
||||
5,
|
||||
10,
|
||||
50
|
||||
],
|
||||
"queries_per_config": 200,
|
||||
"sample_query_locations": 50,
|
||||
"seed": 51966
|
||||
},
|
||||
"runs": [
|
||||
{
|
||||
"entities": 100,
|
||||
"radius": 1,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 1.4766055392101407,
|
||||
"naive_per_query_us": 14.514889917336404,
|
||||
"speedup": 9.829903472460659,
|
||||
"spatial_mean_hits": 0.0,
|
||||
"naive_mean_hits": 0.0
|
||||
},
|
||||
{
|
||||
"entities": 100,
|
||||
"radius": 5,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 1.5784951392561197,
|
||||
"naive_per_query_us": 14.675830025225878,
|
||||
"speedup": 9.297355221595422,
|
||||
"spatial_mean_hits": 0.28,
|
||||
"naive_mean_hits": 0.28
|
||||
},
|
||||
{
|
||||
"entities": 100,
|
||||
"radius": 10,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 1.7352000577375293,
|
||||
"naive_per_query_us": 14.739789767190814,
|
||||
"speedup": 8.49457657718704,
|
||||
"spatial_mean_hits": 0.94,
|
||||
"naive_mean_hits": 0.94
|
||||
},
|
||||
{
|
||||
"entities": 100,
|
||||
"radius": 50,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 3.2524653943255544,
|
||||
"naive_per_query_us": 15.446714824065564,
|
||||
"speedup": 4.749232643955206,
|
||||
"spatial_mean_hits": 15.26,
|
||||
"naive_mean_hits": 15.26
|
||||
},
|
||||
{
|
||||
"entities": 1000,
|
||||
"radius": 1,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 3.663320094347,
|
||||
"naive_per_query_us": 153.2661501551047,
|
||||
"speedup": 41.83804478118501,
|
||||
"spatial_mean_hits": 0.1,
|
||||
"naive_mean_hits": 0.1
|
||||
},
|
||||
{
|
||||
"entities": 1000,
|
||||
"radius": 5,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 2.5340047432109714,
|
||||
"naive_per_query_us": 205.5685903178528,
|
||||
"speedup": 81.12399586804482,
|
||||
"spatial_mean_hits": 1.98,
|
||||
"naive_mean_hits": 1.98
|
||||
},
|
||||
{
|
||||
"entities": 1000,
|
||||
"radius": 10,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 3.4613750176504254,
|
||||
"naive_per_query_us": 156.61109995562583,
|
||||
"speedup": 45.245343008783,
|
||||
"spatial_mean_hits": 7.94,
|
||||
"naive_mean_hits": 7.94
|
||||
},
|
||||
{
|
||||
"entities": 1000,
|
||||
"radius": 50,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 18.54475529398769,
|
||||
"naive_per_query_us": 160.16811016015708,
|
||||
"speedup": 8.6368413937543,
|
||||
"spatial_mean_hits": 153.96,
|
||||
"naive_mean_hits": 153.96
|
||||
},
|
||||
{
|
||||
"entities": 10000,
|
||||
"radius": 1,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 8.608360076323152,
|
||||
"naive_per_query_us": 1619.5069800596684,
|
||||
"speedup": 188.1318817638726,
|
||||
"spatial_mean_hits": 0.92,
|
||||
"naive_mean_hits": 0.92
|
||||
},
|
||||
{
|
||||
"entities": 10000,
|
||||
"radius": 5,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 18.44062004238367,
|
||||
"naive_per_query_us": 2015.9114798298103,
|
||||
"speedup": 109.31907252556947,
|
||||
"spatial_mean_hits": 19.2,
|
||||
"naive_mean_hits": 19.2
|
||||
},
|
||||
{
|
||||
"entities": 10000,
|
||||
"radius": 10,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 32.97713526990265,
|
||||
"naive_per_query_us": 1611.3787598442286,
|
||||
"speedup": 48.86351548295013,
|
||||
"spatial_mean_hits": 75.86,
|
||||
"naive_mean_hits": 75.86
|
||||
},
|
||||
{
|
||||
"entities": 10000,
|
||||
"radius": 50,
|
||||
"queries": 200,
|
||||
"spatial_per_query_us": 346.19326528627425,
|
||||
"naive_per_query_us": 1811.6197548806667,
|
||||
"speedup": 5.232972263000557,
|
||||
"spatial_mean_hits": 1583.42,
|
||||
"naive_mean_hits": 1583.42
|
||||
}
|
||||
]
|
||||
}
|
||||
124
tests/benchmarks/dijkstra_bench.py
Normal file
124
tests/benchmarks/dijkstra_bench.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Benchmark: single-root, multi-root, DiscreteMap-mask Dijkstra plus invert+descent.
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/dijkstra_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import random
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_SIZES = [(100, 100), (500, 500)]
|
||||
ROOT_COUNTS = [1, 2, 5, 20]
|
||||
MASK_DENSITY = 0.05
|
||||
TRIALS = 5
|
||||
SEED = 0x1234
|
||||
|
||||
|
||||
def make_grid(w, h):
|
||||
g = mcrfpy.Grid(grid_size=(w, h))
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
c = g.at(x, y)
|
||||
c.walkable = True
|
||||
c.transparent = True
|
||||
return g
|
||||
|
||||
|
||||
def random_points(w, h, n, rng):
|
||||
pts = set()
|
||||
while len(pts) < n:
|
||||
pts.add((rng.randrange(1, w - 1), rng.randrange(1, h - 1)))
|
||||
return list(pts)
|
||||
|
||||
|
||||
def bench_compute(g, roots, trials):
|
||||
total = 0.0
|
||||
for _ in range(trials):
|
||||
g.clear_dijkstra_maps()
|
||||
t0 = time.perf_counter()
|
||||
_ = g.get_dijkstra_map(roots=roots)
|
||||
total += time.perf_counter() - t0
|
||||
return total / trials
|
||||
|
||||
|
||||
def bench_mask(g, mask, trials):
|
||||
total = 0.0
|
||||
for _ in range(trials):
|
||||
g.clear_dijkstra_maps()
|
||||
t0 = time.perf_counter()
|
||||
_ = g.get_dijkstra_map(roots=mask)
|
||||
total += time.perf_counter() - t0
|
||||
return total / trials
|
||||
|
||||
|
||||
def bench_invert_descent(g, root, trials):
|
||||
d = g.get_dijkstra_map(root)
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(trials):
|
||||
_ = d.invert()
|
||||
invert_t = (time.perf_counter() - t0) / trials
|
||||
|
||||
inv = d.invert()
|
||||
w, h = d.root.x, d.root.y # unused, just reading
|
||||
# Descent throughput: pick many start cells, step once each.
|
||||
starts = [(x, y) for y in range(1, g.grid_h - 1, 10) for x in range(1, g.grid_w - 1, 10)]
|
||||
n = len(starts)
|
||||
t0 = time.perf_counter()
|
||||
ok = 0
|
||||
for _ in range(trials):
|
||||
for s in starts:
|
||||
if inv.descent_step(s) is not None:
|
||||
ok += 1
|
||||
descent_t = (time.perf_counter() - t0) / (trials * n)
|
||||
return invert_t * 1000.0, descent_t * 1e6, ok // trials
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(SEED)
|
||||
out = {"runs": []}
|
||||
|
||||
for (w, h) in GRID_SIZES:
|
||||
g = make_grid(w, h)
|
||||
for n in ROOT_COUNTS:
|
||||
roots = random_points(w, h, n, rng)
|
||||
ms = bench_compute(g, roots, TRIALS) * 1000.0
|
||||
out["runs"].append({"grid": f"{w}x{h}", "kind": "multi_root",
|
||||
"roots": n, "mean_ms": ms})
|
||||
print(f" {w}x{h} multi_root({n:2d}) mean={ms:7.2f} ms")
|
||||
|
||||
# Mask form
|
||||
mask = mcrfpy.DiscreteMap((w, h))
|
||||
n_mask = max(1, int(w * h * MASK_DENSITY))
|
||||
pts = random_points(w, h, n_mask, rng)
|
||||
for (x, y) in pts:
|
||||
mask.set(x, y, 1)
|
||||
ms = bench_mask(g, mask, TRIALS) * 1000.0
|
||||
out["runs"].append({"grid": f"{w}x{h}", "kind": "mask",
|
||||
"roots": n_mask, "mean_ms": ms})
|
||||
print(f" {w}x{h} mask({n_mask}) mean={ms:7.2f} ms")
|
||||
|
||||
# Invert + descent
|
||||
root = (w // 2, h // 2)
|
||||
inv_ms, desc_us, valid = bench_invert_descent(g, root, TRIALS)
|
||||
out["runs"].append({"grid": f"{w}x{h}", "kind": "invert",
|
||||
"mean_ms": inv_ms})
|
||||
out["runs"].append({"grid": f"{w}x{h}", "kind": "descent_step_per_call",
|
||||
"mean_us": desc_us, "valid_per_trial": valid})
|
||||
print(f" {w}x{h} invert mean={inv_ms:7.2f} ms")
|
||||
print(f" {w}x{h} descent_step/call mean={desc_us:7.2f} us valid={valid}")
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("dijkstra_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
144
tests/benchmarks/fov_opt_bench.py
Normal file
144
tests/benchmarks/fov_opt_bench.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""Benchmark: per-entity FOV cost on a 1000x1000 grid (Phase 5.2 / card #37).
|
||||
|
||||
Measures the cost of `entity.update_visibility()` (which writes through the
|
||||
DiscreteMap-backed `entity.perspective_map`, the FOV optimization landed via
|
||||
#294 / commit f797120) versus a bare `grid.compute_fov(...)` call (no
|
||||
per-entity bookkeeping). The delta is the cost of the perspective writeback.
|
||||
|
||||
Configurations:
|
||||
- 100 entities on 1000x1000 grid
|
||||
- radii: 8, 16, 32
|
||||
- FOV algorithms: BASIC, SHADOW, SYMMETRIC_SHADOWCAST
|
||||
|
||||
Output: JSON to stdout; baseline copy written to ./fov_opt_bench_results.json
|
||||
when run from the build/ directory.
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/fov_opt_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import random
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_W, GRID_H = 1000, 1000
|
||||
N_ENTITIES = 100
|
||||
RADII = [8, 16, 32]
|
||||
ALGORITHMS = [
|
||||
("BASIC", mcrfpy.FOV.BASIC),
|
||||
("SHADOW", mcrfpy.FOV.SHADOW),
|
||||
("SYMMETRIC_SHADOWCAST", mcrfpy.FOV.SYMMETRIC_SHADOWCAST),
|
||||
]
|
||||
SEED = 0x1A2B
|
||||
WARMUP_ROUNDS = 1
|
||||
MEASURED_ROUNDS = 3
|
||||
|
||||
|
||||
def build_grid(w, h):
|
||||
g = mcrfpy.Grid(grid_size=(w, h))
|
||||
# Fully-open arena. Walls only on the perimeter.
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
c = g.at(x, y)
|
||||
walkable = (x not in (0, w - 1)) and (y not in (0, h - 1))
|
||||
c.walkable = walkable
|
||||
c.transparent = walkable
|
||||
return g
|
||||
|
||||
|
||||
def place_entities(g, n, rng):
|
||||
ents = []
|
||||
for _ in range(n):
|
||||
x = rng.randrange(1, GRID_W - 1)
|
||||
y = rng.randrange(1, GRID_H - 1)
|
||||
e = mcrfpy.Entity((x, y), grid=g)
|
||||
ents.append(e)
|
||||
return ents
|
||||
|
||||
|
||||
def measure_update_visibility(entities, rounds):
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(rounds):
|
||||
for e in entities:
|
||||
e.update_visibility()
|
||||
return (time.perf_counter() - t0) / rounds
|
||||
|
||||
|
||||
def measure_grid_compute_only(grid, entities, radius, algorithm, rounds):
|
||||
# entity.x/.y are pixel coords (UIDrawable). compute_fov takes grid coords.
|
||||
coords = [(e.grid_pos.x, e.grid_pos.y) for e in entities]
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(rounds):
|
||||
for (x, y) in coords:
|
||||
grid.compute_fov((x, y), radius=radius, light_walls=True, algorithm=algorithm)
|
||||
return (time.perf_counter() - t0) / rounds
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(SEED)
|
||||
print(f"Building {GRID_W}x{GRID_H} grid...")
|
||||
grid = build_grid(GRID_W, GRID_H)
|
||||
entities = place_entities(grid, N_ENTITIES, rng)
|
||||
print(f"Placed {len(entities)} entities.")
|
||||
|
||||
runs = []
|
||||
for (aname, algo) in ALGORITHMS:
|
||||
grid.fov = algo
|
||||
for radius in RADII:
|
||||
grid.fov_radius = radius
|
||||
|
||||
# Warmup (allocates perspective_map + warms TCOD caches).
|
||||
for _ in range(WARMUP_ROUNDS):
|
||||
for e in entities:
|
||||
e.update_visibility()
|
||||
|
||||
with_t = measure_update_visibility(entities, MEASURED_ROUNDS)
|
||||
wo_t = measure_grid_compute_only(grid, entities, radius, algo, MEASURED_ROUNDS)
|
||||
|
||||
with_per_us = with_t / N_ENTITIES * 1e6
|
||||
wo_per_us = wo_t / N_ENTITIES * 1e6
|
||||
overhead_us = with_per_us - wo_per_us
|
||||
|
||||
entry = {
|
||||
"grid": f"{GRID_W}x{GRID_H}",
|
||||
"entities": N_ENTITIES,
|
||||
"algorithm": aname,
|
||||
"radius": radius,
|
||||
"with_perspective_round_ms": with_t * 1000.0,
|
||||
"without_perspective_round_ms": wo_t * 1000.0,
|
||||
"with_perspective_per_entity_us": with_per_us,
|
||||
"without_perspective_per_entity_us": wo_per_us,
|
||||
"perspective_overhead_per_entity_us": overhead_us,
|
||||
}
|
||||
runs.append(entry)
|
||||
print(f" {aname:<22} r={radius:<2} "
|
||||
f"compute={wo_per_us:7.2f} us/ent "
|
||||
f"+perspective={with_per_us:7.2f} us/ent "
|
||||
f"(overhead {overhead_us:+6.2f} us)")
|
||||
|
||||
out = {
|
||||
"config": {
|
||||
"grid": f"{GRID_W}x{GRID_H}",
|
||||
"entities": N_ENTITIES,
|
||||
"radii": RADII,
|
||||
"algorithms": [a[0] for a in ALGORITHMS],
|
||||
"warmup_rounds": WARMUP_ROUNDS,
|
||||
"measured_rounds": MEASURED_ROUNDS,
|
||||
"seed": SEED,
|
||||
},
|
||||
"runs": runs,
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("fov_opt_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
92
tests/benchmarks/grid_step_bench.py
Normal file
92
tests/benchmarks/grid_step_bench.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""Benchmark: grid.step() turn manager with a mix of behaviors.
|
||||
|
||||
100 entities on a 100x100 grid, 1000 rounds. Mix of IDLE / NOISE4 / SEEK / FLEE.
|
||||
Reports total time, mean per-round, p95 per-round.
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/grid_step_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import json
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_W, GRID_H = 100, 100
|
||||
N_ENTITIES = 100
|
||||
N_ROUNDS = 1000
|
||||
SEED = 0x37
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(SEED)
|
||||
scene = mcrfpy.Scene("bench_step")
|
||||
mcrfpy.current_scene = scene
|
||||
grid = mcrfpy.Grid(grid_size=(GRID_W, GRID_H))
|
||||
scene.children.append(grid)
|
||||
|
||||
for y in range(GRID_H):
|
||||
for x in range(GRID_W):
|
||||
c = grid.at(x, y)
|
||||
walkable = (x not in (0, GRID_W - 1)) and (y not in (0, GRID_H - 1))
|
||||
c.walkable = walkable
|
||||
c.transparent = walkable
|
||||
|
||||
# One shared threat / attractor in the center.
|
||||
center = (GRID_W // 2, GRID_H // 2)
|
||||
attractor = grid.get_dijkstra_map(center)
|
||||
safety = attractor.invert()
|
||||
|
||||
for i in range(N_ENTITIES):
|
||||
ex = rng.randrange(1, GRID_W - 1)
|
||||
ey = rng.randrange(1, GRID_H - 1)
|
||||
e = mcrfpy.Entity((ex, ey), grid=grid)
|
||||
e.move_speed = 0
|
||||
mix = i % 4
|
||||
if mix == 0:
|
||||
pass # IDLE default
|
||||
elif mix == 1:
|
||||
e.set_behavior(int(mcrfpy.Behavior.NOISE8))
|
||||
elif mix == 2:
|
||||
e.set_behavior(int(mcrfpy.Behavior.SEEK), pathfinder=attractor)
|
||||
else:
|
||||
e.set_behavior(int(mcrfpy.Behavior.FLEE), pathfinder=safety)
|
||||
|
||||
round_times = []
|
||||
t_suite_start = time.perf_counter()
|
||||
for _ in range(N_ROUNDS):
|
||||
t0 = time.perf_counter()
|
||||
grid.step()
|
||||
round_times.append(time.perf_counter() - t0)
|
||||
total = time.perf_counter() - t_suite_start
|
||||
|
||||
round_times.sort()
|
||||
p95 = round_times[int(0.95 * len(round_times))]
|
||||
per_step_us = (total / N_ROUNDS) / N_ENTITIES * 1e6
|
||||
|
||||
out = {
|
||||
"grid": f"{GRID_W}x{GRID_H}",
|
||||
"entities": N_ENTITIES,
|
||||
"rounds": N_ROUNDS,
|
||||
"total_sec": total,
|
||||
"mean_round_ms": (total / N_ROUNDS) * 1000.0,
|
||||
"p95_round_ms": p95 * 1000.0,
|
||||
"per_entity_step_us": per_step_us,
|
||||
}
|
||||
print(f" total: {total:.2f} s")
|
||||
print(f" mean round: {out['mean_round_ms']:.3f} ms")
|
||||
print(f" p95 round: {out['p95_round_ms']:.3f} ms")
|
||||
print(f" per-entity: {out['per_entity_step_us']:.2f} us")
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("grid_step_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
123
tests/benchmarks/gridview_render_bench.py
Normal file
123
tests/benchmarks/gridview_render_bench.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
"""Benchmark: 1/2/4 GridView instances viewing shared Grid data (Phase 5.2).
|
||||
|
||||
A `mcrfpy.step()` in headless mode does not actually render -- the render path
|
||||
is stubbed. To force a real render we use `automation.screenshot()`, which
|
||||
flushes the current scene to a PNG via the off-screen render target. Each
|
||||
screenshot is one full render of all currently-mounted children.
|
||||
|
||||
We measure mean wall time per screenshot for view counts {1, 2, 4} on the same
|
||||
underlying grid, with grid cells populated to mimic a real overworld scene.
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/gridview_render_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_W, GRID_H = 80, 80
|
||||
N_FRAMES = 60
|
||||
WARMUP_FRAMES = 5
|
||||
VIEW_COUNTS = [1, 2, 4]
|
||||
VIEW_PIXEL_SIZE = (320, 320)
|
||||
|
||||
|
||||
def populate_grid(g):
|
||||
for y in range(GRID_H):
|
||||
for x in range(GRID_W):
|
||||
c = g.at(x, y)
|
||||
c.walkable = True
|
||||
c.transparent = True
|
||||
|
||||
|
||||
def make_scene(view_count, tmpdir):
|
||||
scene = mcrfpy.Scene(f"bench_views_{view_count}")
|
||||
grid = mcrfpy.Grid(grid_size=(GRID_W, GRID_H))
|
||||
populate_grid(grid)
|
||||
views = []
|
||||
for i in range(view_count):
|
||||
# Stack views in a row; tolerate going off-screen, the renderer clips.
|
||||
v = mcrfpy.GridView(
|
||||
grid=grid,
|
||||
pos=(i * 100, 0),
|
||||
size=VIEW_PIXEL_SIZE,
|
||||
)
|
||||
scene.children.append(v)
|
||||
views.append(v)
|
||||
return scene, grid, views
|
||||
|
||||
|
||||
def bench(view_count, tmpdir):
|
||||
scene, grid, views = make_scene(view_count, tmpdir)
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
# Warmup: a few screenshots so any first-time texture loads / shader
|
||||
# compilations are amortised away.
|
||||
for i in range(WARMUP_FRAMES):
|
||||
automation.screenshot(os.path.join(tmpdir, f"warm_{view_count}_{i}.png"))
|
||||
|
||||
times = []
|
||||
for i in range(N_FRAMES):
|
||||
path = os.path.join(tmpdir, f"frame_{view_count}_{i}.png")
|
||||
t0 = time.perf_counter()
|
||||
automation.screenshot(path)
|
||||
times.append(time.perf_counter() - t0)
|
||||
|
||||
times.sort()
|
||||
total = sum(times)
|
||||
mean = total / len(times)
|
||||
p95 = times[int(0.95 * len(times))]
|
||||
|
||||
return {
|
||||
"views": view_count,
|
||||
"frames": N_FRAMES,
|
||||
"warmup_frames": WARMUP_FRAMES,
|
||||
"total_sec": total,
|
||||
"mean_frame_ms": mean * 1000.0,
|
||||
"p95_frame_ms": p95 * 1000.0,
|
||||
"implied_fps": (1.0 / mean) if mean > 0 else float("inf"),
|
||||
"per_view_frame_ms": mean * 1000.0 / view_count,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
runs = []
|
||||
with tempfile.TemporaryDirectory(prefix="mcrf_bench_") as tmpdir:
|
||||
for n in VIEW_COUNTS:
|
||||
r = bench(n, tmpdir)
|
||||
runs.append(r)
|
||||
print(f" views={r['views']} frames={r['frames']} "
|
||||
f"mean={r['mean_frame_ms']:7.2f} ms "
|
||||
f"p95={r['p95_frame_ms']:7.2f} ms "
|
||||
f"fps~{r['implied_fps']:6.1f} "
|
||||
f"per-view={r['per_view_frame_ms']:6.2f} ms")
|
||||
|
||||
base = runs[0]["mean_frame_ms"]
|
||||
print()
|
||||
for r in runs[1:]:
|
||||
ratio = r["mean_frame_ms"] / base if base > 0 else 0
|
||||
print(f" views={r['views']}: total frame time vs 1-view = {ratio:.2f}x")
|
||||
|
||||
out = {"runs": runs, "config": {
|
||||
"grid": f"{GRID_W}x{GRID_H}",
|
||||
"frames": N_FRAMES,
|
||||
"warmup_frames": WARMUP_FRAMES,
|
||||
"view_counts": VIEW_COUNTS,
|
||||
"view_pixel_size": VIEW_PIXEL_SIZE,
|
||||
}}
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("gridview_render_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
178
tests/benchmarks/pathfinding_bench.py
Normal file
178
tests/benchmarks/pathfinding_bench.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
"""Benchmark: find_path() across grid sizes, obstacle densities, heuristics, weights,
|
||||
and with/without collision-label entity blocking.
|
||||
|
||||
Kanban #37 coverage: pathfinding throughput at varying obstacle densities (10/30/50%)
|
||||
plus an explicit with-vs-without collision-label comparison (10 / 100 entities tagged
|
||||
'blocker' on a 100x100 grid).
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/pathfinding_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import random
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_SIZES = [(100, 100), (500, 500)]
|
||||
OBSTACLE_DENSITIES = [0.10, 0.30, 0.50]
|
||||
HEURISTICS = [
|
||||
("EUCLIDEAN", mcrfpy.Heuristic.EUCLIDEAN),
|
||||
("MANHATTAN", mcrfpy.Heuristic.MANHATTAN),
|
||||
("CHEBYSHEV", mcrfpy.Heuristic.CHEBYSHEV),
|
||||
("DIAGONAL", mcrfpy.Heuristic.DIAGONAL),
|
||||
("ZERO", mcrfpy.Heuristic.ZERO),
|
||||
]
|
||||
WEIGHTS = [1.0, 1.5, 2.0]
|
||||
TRIALS_PER_CONFIG = 5
|
||||
|
||||
COLLIDE_GRID = (100, 100)
|
||||
COLLIDE_DENSITY = 0.10
|
||||
COLLIDE_BLOCKER_COUNTS = [0, 10, 100]
|
||||
COLLIDE_TRIALS = 20
|
||||
|
||||
SEED = 0x315
|
||||
|
||||
|
||||
def make_grid(w, h, density, seed):
|
||||
rng = random.Random(seed)
|
||||
g = mcrfpy.Grid(grid_size=(w, h))
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
c = g.at(x, y)
|
||||
walkable = (x in (0, w - 1) or y in (0, h - 1)) or rng.random() > density
|
||||
c.walkable = walkable
|
||||
c.transparent = walkable
|
||||
# Guarantee corners walkable.
|
||||
g.at(1, 1).walkable = True
|
||||
g.at(w - 2, h - 2).walkable = True
|
||||
return g
|
||||
|
||||
|
||||
def pick_endpoints(w, h, rng):
|
||||
return (1, 1), (w - 2, h - 2)
|
||||
|
||||
|
||||
def bench_one(g, start, end, heuristic, weight, collide, trials):
|
||||
total_t = 0.0
|
||||
hits = 0
|
||||
length_sum = 0
|
||||
for _ in range(trials):
|
||||
t0 = time.perf_counter()
|
||||
if collide:
|
||||
p = g.find_path(start, end, heuristic=heuristic, weight=weight, collide=collide)
|
||||
else:
|
||||
p = g.find_path(start, end, heuristic=heuristic, weight=weight)
|
||||
elapsed = time.perf_counter() - t0
|
||||
total_t += elapsed
|
||||
if p is not None:
|
||||
steps = list(p)
|
||||
if steps:
|
||||
hits += 1
|
||||
length_sum += len(steps)
|
||||
return {
|
||||
"mean_ms": (total_t / trials) * 1000.0,
|
||||
"hits": hits,
|
||||
"mean_length": length_sum / max(hits, 1),
|
||||
}
|
||||
|
||||
|
||||
def collide_block_section(rng):
|
||||
"""100x100 grid, walkable arena, tag N entities with 'blocker' label.
|
||||
|
||||
Compares `find_path(..., collide='blocker')` (with) vs `find_path(...)` (without)
|
||||
holding all other variables constant. The same grid is reused across N values,
|
||||
walkable cells are unchanged; only the entity set differs.
|
||||
"""
|
||||
w, h = COLLIDE_GRID
|
||||
g = make_grid(w, h, COLLIDE_DENSITY, rng.randrange(2**31))
|
||||
start, end = pick_endpoints(w, h, rng)
|
||||
|
||||
runs = []
|
||||
for n_blockers in COLLIDE_BLOCKER_COUNTS:
|
||||
# Fresh entity set each iteration. Old entities are garbage-collected once
|
||||
# the local list goes out of scope.
|
||||
entities = []
|
||||
for _ in range(n_blockers):
|
||||
while True:
|
||||
ex = rng.randrange(2, w - 2)
|
||||
ey = rng.randrange(2, h - 2)
|
||||
if g.at(ex, ey).walkable and (ex, ey) not in (start, end):
|
||||
break
|
||||
e = mcrfpy.Entity((ex, ey), grid=g)
|
||||
e.add_label("blocker")
|
||||
entities.append(e)
|
||||
|
||||
# WITHOUT collide arg (entities present but ignored).
|
||||
wo = bench_one(g, start, end, mcrfpy.Heuristic.EUCLIDEAN, 1.0, None, COLLIDE_TRIALS)
|
||||
# WITH collide arg.
|
||||
wi = bench_one(g, start, end, mcrfpy.Heuristic.EUCLIDEAN, 1.0, "blocker", COLLIDE_TRIALS)
|
||||
|
||||
runs.append({
|
||||
"grid": f"{w}x{h}",
|
||||
"blockers": n_blockers,
|
||||
"without_collide_ms": wo["mean_ms"],
|
||||
"with_collide_ms": wi["mean_ms"],
|
||||
"without_collide_path_len": wo["mean_length"],
|
||||
"with_collide_path_len": wi["mean_length"],
|
||||
"overhead_ms": wi["mean_ms"] - wo["mean_ms"],
|
||||
})
|
||||
print(f" collide n={n_blockers:<3} "
|
||||
f"without={wo['mean_ms']:6.2f} ms (len={wo['mean_length']:.0f}) "
|
||||
f"with={wi['mean_ms']:6.2f} ms (len={wi['mean_length']:.0f}) "
|
||||
f"overhead={wi['mean_ms'] - wo['mean_ms']:+6.2f} ms")
|
||||
|
||||
# Drop entities from the grid before next iteration so the count is correct.
|
||||
for e in entities:
|
||||
e.die()
|
||||
del entities
|
||||
|
||||
return runs
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(SEED)
|
||||
out = {"config": {
|
||||
"grid_sizes": GRID_SIZES,
|
||||
"obstacle_densities": OBSTACLE_DENSITIES,
|
||||
"heuristics": [h[0] for h in HEURISTICS],
|
||||
"weights": WEIGHTS,
|
||||
"trials": TRIALS_PER_CONFIG,
|
||||
"collide_blocker_counts": COLLIDE_BLOCKER_COUNTS,
|
||||
"collide_trials": COLLIDE_TRIALS,
|
||||
}, "runs": []}
|
||||
|
||||
for (w, h) in GRID_SIZES:
|
||||
for density in OBSTACLE_DENSITIES:
|
||||
seed = rng.randrange(2**31)
|
||||
g = make_grid(w, h, density, seed)
|
||||
start, end = pick_endpoints(w, h, rng)
|
||||
for (hname, heuristic) in HEURISTICS:
|
||||
for weight in WEIGHTS:
|
||||
r = bench_one(g, start, end, heuristic, weight, None, TRIALS_PER_CONFIG)
|
||||
out["runs"].append({
|
||||
"grid": f"{w}x{h}", "density": density,
|
||||
"heuristic": hname, "weight": weight,
|
||||
"collide": None, **r,
|
||||
})
|
||||
print(f" {w}x{h} d={density:.2f} h={hname:<9} w={weight:.1f} "
|
||||
f"mean={r['mean_ms']:7.2f} ms len={r['mean_length']:.1f}")
|
||||
|
||||
print()
|
||||
print(f"=== Collision-label comparison ({COLLIDE_GRID[0]}x{COLLIDE_GRID[1]}, "
|
||||
f"{COLLIDE_TRIALS} trials/config) ===")
|
||||
out["collide_runs"] = collide_block_section(rng)
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("pathfinding_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
163
tests/benchmarks/spatial_hash_bench.py
Normal file
163
tests/benchmarks/spatial_hash_bench.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"""Benchmark: spatial-hash query throughput (Phase 5.2 / card #37, scenario 3).
|
||||
|
||||
Per acceptance criteria: `queryRadius()` at radii (1, 5, 10, 50) with
|
||||
(100, 1000, 10000) entities. The Python-facing call is `grid.entities_in_radius()`.
|
||||
|
||||
For each (count, radius) pair we measure:
|
||||
- mean per-query time (us)
|
||||
- O(n) baseline (manual scan of `grid.entities`)
|
||||
- speedup factor
|
||||
- mean hit count
|
||||
|
||||
Headless mode. Output: JSON to stdout.
|
||||
|
||||
Usage:
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/spatial_hash_bench.py
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import random
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _baseline
|
||||
|
||||
|
||||
GRID_W, GRID_H = 200, 200
|
||||
ENTITY_COUNTS = [100, 1000, 10000]
|
||||
RADII = [1, 5, 10, 50]
|
||||
QUERIES_PER_CONFIG = 200
|
||||
SAMPLE_QUERY_LOCATIONS = 50 # how many distinct (x,y) sample positions
|
||||
SEED = 0xCAFE
|
||||
|
||||
|
||||
def build_grid(w, h):
|
||||
g = mcrfpy.Grid(grid_size=(w, h))
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
c = g.at(x, y)
|
||||
c.walkable = True
|
||||
c.transparent = True
|
||||
return g
|
||||
|
||||
|
||||
def populate(g, n, rng):
|
||||
ents = []
|
||||
seen = set()
|
||||
while len(ents) < n:
|
||||
x = rng.randrange(GRID_W)
|
||||
y = rng.randrange(GRID_H)
|
||||
if (x, y) in seen:
|
||||
continue
|
||||
seen.add((x, y))
|
||||
ents.append(mcrfpy.Entity((x, y), grid=g))
|
||||
return ents
|
||||
|
||||
|
||||
def sample_points(rng, n):
|
||||
return [(rng.randrange(GRID_W), rng.randrange(GRID_H)) for _ in range(n)]
|
||||
|
||||
|
||||
def bench_spatial(g, points, radius, queries):
|
||||
"""Mean per-query time using SpatialHash-backed entities_in_radius."""
|
||||
n_pts = len(points)
|
||||
hits_total = 0
|
||||
t0 = time.perf_counter()
|
||||
for i in range(queries):
|
||||
result = g.entities_in_radius(points[i % n_pts], radius)
|
||||
hits_total += len(result)
|
||||
elapsed = time.perf_counter() - t0
|
||||
return elapsed / queries, hits_total / queries
|
||||
|
||||
|
||||
def bench_naive(g, points, radius, queries):
|
||||
"""O(n) baseline: enumerate grid.entities and check distance manually.
|
||||
|
||||
Note: `entity.x`/`entity.y` are pixel coordinates inherited from UIDrawable.
|
||||
We compare against `entity.grid_pos`, which is the same coordinate frame
|
||||
`entities_in_radius` uses.
|
||||
"""
|
||||
n_pts = len(points)
|
||||
r2 = radius * radius
|
||||
# Snapshot grid coordinates so the loop body has no Python attribute lookup
|
||||
# cost beyond the unavoidable.
|
||||
coords = [(e.grid_pos.x, e.grid_pos.y) for e in g.entities]
|
||||
hits_total = 0
|
||||
t0 = time.perf_counter()
|
||||
for i in range(queries):
|
||||
cx, cy = points[i % n_pts]
|
||||
n = 0
|
||||
for ex, ey in coords:
|
||||
dx = ex - cx
|
||||
dy = ey - cy
|
||||
if dx * dx + dy * dy <= r2:
|
||||
n += 1
|
||||
hits_total += n
|
||||
elapsed = time.perf_counter() - t0
|
||||
return elapsed / queries, hits_total / queries
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(SEED)
|
||||
runs = []
|
||||
for n_ent in ENTITY_COUNTS:
|
||||
scene = mcrfpy.Scene(f"spatial_{n_ent}")
|
||||
mcrfpy.current_scene = scene
|
||||
g = build_grid(GRID_W, GRID_H)
|
||||
scene.children.append(g)
|
||||
ents = populate(g, n_ent, rng)
|
||||
pts = sample_points(rng, SAMPLE_QUERY_LOCATIONS)
|
||||
|
||||
for radius in RADII:
|
||||
# Warmup the spatial hash for this entity set.
|
||||
for p in pts:
|
||||
g.entities_in_radius(p, radius)
|
||||
|
||||
sp_t, sp_hits = bench_spatial(g, pts, radius, QUERIES_PER_CONFIG)
|
||||
nv_t, nv_hits = bench_naive(g, pts, radius, QUERIES_PER_CONFIG)
|
||||
speedup = (nv_t / sp_t) if sp_t > 0 else float("inf")
|
||||
|
||||
entry = {
|
||||
"entities": n_ent,
|
||||
"radius": radius,
|
||||
"queries": QUERIES_PER_CONFIG,
|
||||
"spatial_per_query_us": sp_t * 1e6,
|
||||
"naive_per_query_us": nv_t * 1e6,
|
||||
"speedup": speedup,
|
||||
"spatial_mean_hits": sp_hits,
|
||||
"naive_mean_hits": nv_hits,
|
||||
}
|
||||
runs.append(entry)
|
||||
print(f" n={n_ent:>5} r={radius:<3} "
|
||||
f"spatial={sp_t * 1e6:9.2f} us "
|
||||
f"naive={nv_t * 1e6:10.2f} us "
|
||||
f"speedup={speedup:7.2f}x "
|
||||
f"hits={sp_hits:6.1f} (naive={nv_hits:6.1f})")
|
||||
|
||||
# Tear down entities so they don't leak into the next iteration's grid.
|
||||
for e in ents:
|
||||
e.die()
|
||||
del ents
|
||||
del g
|
||||
|
||||
out = {
|
||||
"config": {
|
||||
"grid": f"{GRID_W}x{GRID_H}",
|
||||
"entity_counts": ENTITY_COUNTS,
|
||||
"radii": RADII,
|
||||
"queries_per_config": QUERIES_PER_CONFIG,
|
||||
"sample_query_locations": SAMPLE_QUERY_LOCATIONS,
|
||||
"seed": SEED,
|
||||
},
|
||||
"runs": runs,
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
_baseline.write("spatial_hash_bench.json", out)
|
||||
print("DONE")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue