Skip to content

Commit 78ac056

Browse files
committed
Update partial cache benchmark
1 parent ab08f07 commit 78ac056

2 files changed

Lines changed: 65 additions & 49 deletions

File tree

tests/benchmarking/adaptors.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

tests/benchmarking/test_benchmarking.py

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
import uuid
2+
import random
13
import pytest
24
import json
35
import importlib
46
from pathlib import Path
57

6-
from tests.config import override_settings
78
from grimp.application.graph import ImportGraph
89
from grimp import PackageDependency, Route
910
import grimp
1011
from copy import deepcopy
11-
from .adaptors import PrefixMissingCache
1212

1313

1414
@pytest.fixture(scope="module")
@@ -55,14 +55,16 @@ def large_graph():
5555
middle=(),
5656
tails=frozenset(
5757
{
58-
"mypackage.application.7537183614.6928774480.5676105139.3275676604" # noqa:E501
58+
"mypackage.application.7537183614.6928774480.5676105139.3275676604"
59+
# noqa:E501
5960
}
6061
),
6162
),
6263
Route(
6364
heads=frozenset(
6465
{
65-
"mypackage.domain.6928774480.5676105139.1330171288.7588443317.4661445087" # noqa:E501
66+
"mypackage.domain.6928774480.5676105139.1330171288.7588443317.4661445087"
67+
# noqa:E501
6668
}
6769
),
6870
middle=(),
@@ -83,7 +85,8 @@ def large_graph():
8385
Route(
8486
heads=frozenset(
8587
{
86-
"mypackage.domain.6928774480.1028759677.7960519247.2888779155.7486857426" # noqa:E501
88+
"mypackage.domain.6928774480.1028759677.7960519247.2888779155.7486857426"
89+
# noqa:E501
8790
}
8891
),
8992
middle=(),
@@ -135,15 +138,19 @@ def large_graph():
135138
Route(
136139
heads=frozenset(
137140
{
138-
"mypackage.application.7537183614.2538372545.1153384736.6297289996", # noqa:E501
139-
"mypackage.application.7537183614.2538372545.1153384736.6404547812.6297289996", # noqa:E501
141+
"mypackage.application.7537183614.2538372545.1153384736.6297289996",
142+
# noqa:E501
143+
"mypackage.application.7537183614.2538372545.1153384736.6404547812.6297289996",
144+
# noqa:E501
140145
}
141146
),
142147
middle=("mypackage.6398020133.9075581450.6529869526.6297289996",),
143148
tails=frozenset(
144149
{
145-
"mypackage.plugins.5634303718.6180716911.7582995238.1039461003.2943193489", # noqa:E501
146-
"mypackage.plugins.5634303718.6180716911.7582995238.1039461003.6322703811", # noqa:E501
150+
"mypackage.plugins.5634303718.6180716911.7582995238.1039461003.2943193489",
151+
# noqa:E501
152+
"mypackage.plugins.5634303718.6180716911.7582995238.1039461003.6322703811",
153+
# noqa:E501
147154
}
148155
),
149156
)
@@ -319,36 +326,65 @@ def test_build_django_from_cache_no_misses(benchmark):
319326
350, # Around half the Django codebase.
320327
),
321328
)
322-
def test_build_django_from_cache_a_few_misses(benchmark, number_of_misses):
329+
def test_build_django_from_cache_a_few_misses(benchmark, number_of_misses: int):
323330
"""
324331
Benchmarks building a graph of real package - in this case Django.
325332
326333
This benchmark utilizes the cache except for a few modules, which we add.
327334
"""
328-
# We must use a special cache class, otherwise the cache will be populated
329-
# by the first iteration. It would be better to do this using a setup function,
330-
# which is supported by pytest-benchmark's pedantic mode, but not codspeed.
331-
# This won't give us a truly accurate picture, but it's better than nothing.
335+
# We need to take care in benchmarking partially populated caches, because
336+
# the benchmark may run multiple times, depending on the context in which it's run.
337+
# If we're not careful, the cache will be populated the first time and not reset
338+
# in subsequent runs.
339+
#
340+
# The benchmark fixture available here is either from pytest-benchmark (used locally)
341+
# or pytest-codspeed (used in CI). Here's how they both behave:
342+
#
343+
# - pytest-benchmark: By default, dynamically decides how many times to run the benchmark.
344+
# It does this to improve benchmarking of very fast single runs: it will run
345+
# the code many times and average the total time. That's not so important
346+
# for code that takes orders of magnitude longer than the timer resolution.
347+
#
348+
# We can override this by using benchmark.pedantic, where we specify the
349+
# number of rounds and iterations. Each iteration contains multiple rounds.
350+
#
351+
# It's also possible to provide a setup function that runs
352+
# between each iteration. A teardown function will be in the next release
353+
# but isn't in pytest-benchmark<=5.1.0.
354+
#
355+
# - pytest-codspeed: This can run in two modes, CPU instrumentation and wall time. Currently
356+
# we use CPU instrumentation as wall time is only available to
357+
# Github organizations. CPU mode will always run the benchmark once,
358+
# regardless of what rounds and iterations are specified in pedantic mode.
359+
# This mode measures the speed of the benchmark by simulating the CPU,
360+
# rather than timing how long it actually takes, so there is no point in
361+
# running it multiple times and taking an average.
362+
#
363+
# So - in this case, because we are benchmarking a relatively slow piece of code, we explicitly
364+
# turn off multiple runs, which could potentially be misleading when running locally.
332365

333-
# Add some specially-named modules which will be treated as not in the cache.
334-
django_path = Path(importlib.util.find_spec("django").origin).parent
335-
extra_modules = [
336-
django_path / f"{PrefixMissingCache.MISSING_PREFIX}{i}.py" for i in range(number_of_misses)
337-
]
338-
# Use some real python, which will take time to parse.
366+
# Populate the cache first, before beginning the benchmark.
367+
grimp.build_graph("django")
368+
# Add some modules which won't be in the cache.
369+
# (Use some real python, which will take time to parse.)
370+
django_path = Path(importlib.util.find_spec("django").origin).parent # type: ignore
339371
module_to_copy = django_path / "forms" / "forms.py"
340372
module_contents = module_to_copy.read_text()
341-
for extra_module in extra_modules:
342-
extra_module.write_text(module_contents)
343-
344-
with override_settings(CACHE_CLASS=PrefixMissingCache):
345-
# Populate the cache.
346-
grimp.build_graph("django")
373+
extra_modules = [
374+
django_path / f"module_{i}_{random.randint(100000, 999999)}.py"
375+
for i in range(number_of_misses)
376+
]
377+
for new_module in extra_modules:
378+
# Make sure the module contents aren't identical. Depending on how the cache is implemented,
379+
# perhaps this could make a difference.
380+
hash_buster = f"\n# Hash busting comment: {uuid.uuid4()}"
381+
new_module.write_text(module_contents + hash_buster)
347382

348-
benchmark(grimp.build_graph, "django")
383+
benchmark.pedantic(grimp.build_graph, ["django"], rounds=1, iterations=1)
349384

350-
# Clean up.
351-
[module.unlink() for module in extra_modules]
385+
# Delete the modules we just created.
386+
for module in extra_modules:
387+
module.unlink()
352388

353389

354390
class TestFindIllegalDependenciesForLayers:

0 commit comments

Comments
 (0)