Skip to content

Conversation

@Peter554
Copy link
Collaborator

@Peter554 Peter554 commented Jan 18, 2025

This PR adds some more benchmarks. I'm making some progress on the rust graph here, but it would be helpful to have a few more benchmarks in place so that we can measure more things. Currently we only have building the graph and illegal layers - there's quite a lot of ground inbetween and the gap feels a bit daunting!

It also adds a fix for the existing benchmarks, when building the large graph.

pytest-benchmark benchmark() function returns the results,
so we can assert within the same test.

def test_find_shortest_chain(large_graph, benchmark):
result = benchmark(large_graph.find_shortest_chain, DEEP_LAYERS[0], DEEP_LAYERS[1])
assert result is not None and len(result) == 5
Copy link
Collaborator Author

@Peter554 Peter554 Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to assert against the exact result here. This file is intended for benchmarks, not full tests, so a quick sanity check seems fine to me.

Copy link
Collaborator Author

@Peter554 Peter554 Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option could be to use snapshots, so that we can assert the exact results but they are not cluttering the benchmarks https://github.com/syrupy-project/syrupy

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to have it somewhere, at some point (to check feature parity between this and the Rust graph implementation) but I take your point, it could go somewhere else. Don't feel strongly about syrupy either way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried asserting against the exact result here, but it doesn't seem possible - find_shortest_chain/find_shortest_chains seem to return different results between runs? I guess there must be multiple chains of equal length?

@Peter554 Peter554 force-pushed the more-benchmarks branch 3 times, most recently from 73e089a to 303e910 Compare January 18, 2025 11:55
containers=("mypackage",),
)
)
assert result == {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather we didn't remove these - we don't actually assert on the exact results of a large graph like this anywhere else and it does give me more confidence that things are happening correctly. Happy for it to be moved out of benchmarks if you feel strongly, but maybe we should leave that for a separate PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay - put this back

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 18, 2025

CodSpeed Performance Report

Merging #177 will degrade performances by 66.81%

Comparing Peter554:more-benchmarks (455fed2) with master (957ff81)

Summary

❌ 2 (👁 2) regressions
✅ 2 untouched benchmarks
🆕 7 new benchmarks

Benchmarks breakdown

Benchmark master Peter554:more-benchmarks Change
🆕 test_chain_found N/A 67.5 µs N/A
🆕 test_no_chain N/A 39.8 µs N/A
🆕 test_chains_found N/A 128 ms N/A
🆕 test_no_chains N/A 128.7 ms N/A
👁 test_deep_layers_large_graph 1.3 s 1.5 s -14.18%
🆕 test_find_descendants N/A 72.6 ms N/A
🆕 test_find_downstream_modules N/A 2.5 s N/A
🆕 test_find_upstream_modules N/A 1.8 s N/A
👁 test_top_level_large_graph 260.8 ms 785.8 ms -66.81%

Copy link
Collaborator

@seddonym seddonym left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at this, great addition.

For some reason the layer checks have become a lot slower though, any idea why? Maybe it's worth separating out commits adding the new benchmarks so we can add ASAP, then keep the other changes separate. Maybe it's a blip.

graph = ImportGraph()

for importer, importeds in graph_dict.items():
graph.add_module(importer)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know why this changes the result? Adding the import should add any modules as per https://grimp.readthedocs.io/en/stable/usage.html#ImportGraph.add_import

To me this looks like we may have found a bug.

Copy link
Collaborator Author

@Peter554 Peter554 Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure I understand why this changed the results and caused the performance slowdown.

One thing that is happening - the graph is now bigger. E.g. consider

{
  "pkg.a": ["pkg.b"],
  "pkg.c": []
}

In the above JSON, the pkg.c module would not have been added before (since it has no imports, and nothing is importing it).

I've reordered the commits, so that the changes caused e.g. in number of modules becomes more obvious now.

Even though I don't fully understand why this happens entirely, I am entirely confident in this change to the benchmarks file. I.e. we certainly should be calling graph.add_module(importer), and the import path that we now find is correct (agrees with pyimports). So I think there's no harm to avoid merging this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I understand it now.

result = large_graph.find_illegal_dependencies_for_layers(
    layers=(
        # mypackage.foo and mypackage.bar do not exist!!!
        "foo",
        "bar",
    ),
    containers=("mypackage",),
)
assert result == set()

The above passes ☝️ So it looks like find_illegal_dependencies_for_layers does not error if the layer modules cannot be found in the graph. Presumably this is by design?

If we look at large graph JSON we have e.g.

"mypackage.domain": [],

This is the only occurence of "mypackage.domain" in that file. So - mypackage.domain is not importing anything, and nothing is importing it - hence the module is not added to the graph.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So sadly the test_top_level_large_graph was not doing anything before 😅 Hence why it is now slower - because it actually does some work!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha ok, makes sense.

The silent failure is by design only to the point of feature parity, but I think it would be worth changing it at some point. Not now though.

containers=("mypackage",),
)
)
assert result == {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather we didn't remove these - we don't actually assert on the exact results of a large graph like this anywhere else and it does give me more confidence that things are happening correctly. Happy for it to be moved out of benchmarks if you feel strongly, but maybe we should leave that for a separate PR.

benchmark(fn)


def test_find_descendants(large_graph, benchmark):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth using benchmark.pedantic for each of these, like with the others, so we get to average out the runs. That will only apply to a local run.

Copy link
Collaborator Author

@Peter554 Peter554 Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done it.

🐼 I'm not fully sure what this does. Even without pedantic pytest-benchmark will run many iterations and take an average. What's the difference between between iterations and rounds here?


def test_find_shortest_chain(large_graph, benchmark):
result = benchmark(large_graph.find_shortest_chain, DEEP_LAYERS[0], DEEP_LAYERS[1])
assert result is not None and len(result) == 5
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to have it somewhere, at some point (to check feature parity between this and the Rust graph implementation) but I take your point, it could go somewhere else. Don't feel strongly about syrupy either way.

assert result is None


def test_find_shortest_chains(large_graph, benchmark):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind grouping these tests for the same method into a TestFindShortestChains class?

def test_find_descendants(large_graph, benchmark):
result = _run_benchmark(benchmark, large_graph.find_descendants, "mypackage")
assert len(result) == 17348
assert len(result) == 28222
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @seddonym - see here that fixing the graph increases the number of modules. I.e. we were missing many modules before.

)
assert result is not None

@pytest.mark.xfail("grimp.exceptions.ModuleNotPresent")
Copy link
Collaborator Author

@Peter554 Peter554 Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @seddonym - see here that fixing the graph means that the module mypackage.data.vendors.4053192739.6373932949 now exists, as it should!

@Peter554 Peter554 requested a review from seddonym January 18, 2025 14:58
Copy link
Collaborator

@seddonym seddonym left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff

@seddonym seddonym merged commit 6523dfe into python-grimp:master Jan 18, 2025
17 checks passed
@Peter554 Peter554 deleted the more-benchmarks branch February 7, 2025 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants