kmod: eliminate duplication in calls to modinfo#4392
Conversation
|
Thanks for the contribution! I will only have time to look at this later this week, but I started the CI run to see whether it works. |
behrmann
left a comment
There was a problem hiding this comment.
Thank you! Sorry for the lag on my end.
e9a067c to
2528c88
Compare
|
I've sat down an ran a tiny bit of benchmarking on this and running this against my host system, with empty inputs as required_kernel_modules(kver, modules_include=[], modules_exclude=[], firmware_include=[], firmware_exclude=[], resolve_func=resolve_module_dependencies)after making the function to resolve module dependencies pluggable to easily compare this, I get roughly the same run time for both functions, with the version from this PR actually a tiny bit, but consistently, slower. At first I thought it was the chunking and I changed that to which is roughly That helps a bit, but doesn't get one all the way, but the real trick is in lifting the todo update Since Could you check that this makes sense and works in your usecase? |
aa43b78 to
e2594b6
Compare
|
Hi and thanks for looking at my PR. How much benefit this does in practice depends both on the specifics of your With your change I see smaller 0-10% faster here, and your version is also cleaner so I'm happy to include your suggestion. I also added a Co-Authored-By. |
| todo = [*builtin, *modules] | ||
| mods = set() | ||
| todo = {*builtin, *modules} | ||
| mods : set[str] = set() |
There was a problem hiding this comment.
Is this annotation necessary? I don't see mypy (or pyright or ty) complaning about this. My mypy version is 2.1. We do avoid unneeded annotations.
If it were necessary, there would be this small nit
| mods : set[str] = set() | |
| mods: set[str] = set() |
There was a problem hiding this comment.
I removed the space but yes I get mypy error.
mypy --version
mypy 2.1.0 (compiled: yes)
mypy mkosi
pyproject.toml: [mypy]: python_version: Python 3.9 is not supported (must be 3.10 or higher). You may need to put quotes around your Python version
mkosi/kmod.py:296:5: error: Need type annotation for "mods" (hint: "mods: set[<type>] = ...") [var-annotated]
mods = set()
^~~~
Found 1 error in 1 file (checked 52 source files)
| tochunk = tuple(todo) | ||
| # We could run modinfo once for each module but that's slow. Luckily we can pass multiple modules | ||
| # to modinfo and it'll process them all in a single go. We get the modinfo for all modules to | ||
| # build a map that maps the module name to both its module dependencies and its firmware | ||
| # dependencies. Because there's more kernel modules than the max number of accepted CLI | ||
| # arguments, we split the modules list up into chunks if needed. | ||
| for i in range(0, len(todo), 8500): | ||
| chunk = todo[i : i + 8500] | ||
| chunk = tochunk[i : i + 8500] | ||
| moddep |= modinfo(context, kver, chunk) |
There was a problem hiding this comment.
Could you also incorporate this?
# We could run modinfo once for each module but that's slow. Luckily we can pass multiple modules
# to modinfo and it'll process them all in a single go. We get the modinfo for all modules to
# build a map that maps the module name to both its module dependencies and its firmware
# dependencies. Because there's more kernel modules than the max number of accepted CLI
# arguments, we split the modules list up into chunks if needed.
# TODO: use itertools.batched starting with 3.12
ittodo = iter(todo)
while chunk := tuple(itertools.islice(ittodo, 8500)):
moddep |= modinfo(kver, chunk)There was a problem hiding this comment.
you probably meant
moddep |= modinfo(context, kver, chunk)
The original guard when building the new todo is: ``` todo += [m for m in depinfo.modules if m not in mods and m in nametofile] ``` `mods` only accumulates modules from previous iterations of the outer while loop. Within a single iteration, modules in the current batch are added to mods one at a time as `moddep.items()` is iterated. This means two distinct failure modes: 1. Cross-dependency within a batch: if modules A and B are both in the current todo and both depend on X, X passes the m not in mods check when processing A's deps and again when processing B's (since X hasn't been added to mods yet). X ends up in todo twice. 2. Intra-batch back-edge: if A depends on B and both are already in the current todo/moddep, B still passes m not in mods when processing A's dep list, queuing B for a redundant second modinfo call in the next iteration. Both boil down to the same root cause: the check doesn't account for modules currently being processed. The fix addresses both issues: 1. `m not in moddep.keys()` prevents modules from the current batch being re-queued into the next iteration's `todo`. 2. Turning`todo` into a set prevents a module from appearing *multiple times* in the same next `todo`, when two modules in the current batch share a dependency Because these duplicates frequently occur in practice, eliminating them provides an incremental speedup on top of the original optimization made in GH4092/GH4017. Co-Authored-By: Jörg Behrmann <behrmann@physik.fu-berlin.de>
e2594b6 to
9f67b03
Compare
The original guard when building the new todo is:
modsonly accumulates modules from previous iterations of the outer while loop. Within a single iteration, modules in the current batch are added to mods one at a time asmoddep.items()is iterated. This means two distinct failure modes:Cross-dependency within a batch: if modules A and B are both in the current todo and both depend on X, X passes the m not in mods check when processing A's deps and again when processing B's (since X hasn't been added to mods yet). X ends up in todo twice.
Intra-batch back-edge: if A depends on B and both are already in the current todo/moddep, B still passes m not in mods when processing A's dep list, queuing B for a redundant second modinfo call in the next iteration.
Both boil down to the same root cause: the check doesn't account for modules currently being processed.
The fix addresses both issues:
m not in moddep.keys()prevents modules from the current batch being re-queued into the next iteration'stodo.Turning
todointo a set prevents a module from appearing multiple times in the same nexttodo, when two modules in the current batch share a dependencyBecause these duplicates frequently occur in practice, eliminating them provides an incremental speedup on top of the original optimization made in GH4092/GH4017.