Tests: gc.freeze() by default in the test\benchmark\locations.py (#5055)

Without `gc.freeze()` and `gc.unfreeze()` afterward, the `gc.collect()`
call within each benchmark often takes much longer than all 100_000
iterations of the location access rule, making it difficult to benchmark
all but the slowest of access rules.

This change enables using `gc.freeze()` by default.
This commit is contained in:
Mysteryem
2025-10-10 16:19:52 +01:00
committed by GitHub
parent 30ce74d6d5
commit d3588a057c

View File

@@ -1,4 +1,12 @@
def run_locations_benchmark(): def run_locations_benchmark(freeze_gc: bool = True) -> None:
"""
Run a benchmark of location access rule performance against an empty_state and an all_state.
:param freeze_gc: Whether to freeze gc before benchmarking and unfreeze gc afterward. Freezing gc moves all objects
tracked by the garbage collector to a permanent generation, ignoring them in all future collections. Freezing
greatly reduces the duration of running gc.collect() within benchmarks, which otherwise often takes much longer
than running all iterations for the location rule being benchmarked.
"""
import argparse import argparse
import logging import logging
import gc import gc
@@ -34,6 +42,8 @@ def run_locations_benchmark():
return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top)) return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float: def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float:
if freeze_gc:
gc.freeze()
with TimeIt(f"{test_location.game} {self.rule_iterations} " with TimeIt(f"{test_location.game} {self.rule_iterations} "
f"runs of {test_location}.access_rule({state_name})", logger) as t: f"runs of {test_location}.access_rule({state_name})", logger) as t:
for _ in range(self.rule_iterations): for _ in range(self.rule_iterations):
@@ -41,6 +51,8 @@ def run_locations_benchmark():
# if time is taken to disentangle complex ref chains, # if time is taken to disentangle complex ref chains,
# this time should be attributed to the rule. # this time should be attributed to the rule.
gc.collect() gc.collect()
if freeze_gc:
gc.unfreeze()
return t.dif return t.dif
def main(self): def main(self):
@@ -64,9 +76,13 @@ def run_locations_benchmark():
gc.collect() gc.collect()
for step in self.gen_steps: for step in self.gen_steps:
if freeze_gc:
gc.freeze()
with TimeIt(f"{game} step {step}", logger): with TimeIt(f"{game} step {step}", logger):
call_all(multiworld, step) call_all(multiworld, step)
gc.collect() gc.collect()
if freeze_gc:
gc.unfreeze()
locations = sorted(multiworld.get_unfilled_locations()) locations = sorted(multiworld.get_unfilled_locations())
if not locations: if not locations: