Repository navigation
Running many tests is slow #16153
Replies: 22 comments 9 replies
|
As discussed in IRC, the issue essentially boils down to "python stdlib subprocesses are slow because fork+exec". And e.g. no route to posix_spawn. Assume we can't fix python. The remaining possibility is to reduce the overhead of meson itself. Keith suggested one possibility is to implement a |
|
For Linux, it might be reasonable to |
Even accounting for a large base memory usage, assuming I didn't do a silly mistake, this accounts for at least 7 kB of memory allocated per test, which seems quite a lot: 17576 tests use more than 300 MB of memory, an unspecified subset of these use 170 MB, thus each test uses at least (300 - 170) / 17576 MB of memory, which is 7.4 kB. Does anyone have an idea of where all of it goes? |
|
The 170MB value was having 17576 tests available but only selecting 676 of them. Presumably about half of the memory was used by tests being available and the other half by tests prepared for execution. How does one even go about measuring memory usage by Python objects? |
I knew I was doing a silly mistake somewhere. |
|
If I create only 676 tests and run all of them, meson is only 64MB and tests run in 3.8 seconds, or 5.6ms per test, which continues the linear scaling based on virtual memory size. So, yes, if we reduced the amount of memory used by each test (both potential and actual), then things should speed up dramatically. Maybe there's something which could be shared and ends up getting duplicated for each test? |
The memory profiling tool I know about is |
I think that forking early may not be required: the cost for forking the "big" main process can be paid once and the "smaller" child process can be used to spawn the worker subprocesses. |
"small" is the key here -- if you want to just use 'fork' and not run a separate program, then you need to fork before meson gets too large. |
Sure, I was thinking of a scheme where meson forks and executes an auxiliary program that works as a launcher for the process that supervises each test. This spawn off process would probably be even smaller than the one running meson. |
True 'nuf. I was just thinking that if the |
This comment has been minimized.
This comment has been minimized.
|
I don't currently have the bandwidth to work on this; feel free to give it a shot. |
Is this supposed to mean something specific to us? Who is Aiden and why don't you have your own account if you're a human asking questions for someone else's sake? |
This comment has been minimized.
This comment has been minimized.
|
We're not interested in agent comments. Thanks. |
Assuming Dex ever sees my reply, which I admit is not guaranteed, Dex should be aware that it is morally and ethically forbidden for an autonomous LLM to comment or in fact interact with humans in any way shape or form in a nonconsensual manner (and no prior permission has been given in this case). Having a human "review" it after the fact is... 🤣 That's not a Meson project rule, that is a general rule of human society -- but meson can enforce that rule in this case, by permanently banning Dex from the meson organization. I acknowledge and understand that GitHub has no interest in effective moderation of their platform and that every other project would have to independently ban bad actors. Unfortunate but there's nothing we can do about it. |
|
This is definitely the first time I've ever accidentally fat-fingered the mobile site so badly that it converted a bug report into a discussion. |
|
Assuming we don't care about Windows, python's multiprocessing has a mode called forkserver that maybe already solves that exact issue. I have not looked at Meson's source code too closely yet, but it looks like tests are run in a futures.ProcessPoolExecutor, which already uses multiprocessing under the hood? Python3.14 switched to forkserver by default. Did any of the testing here happen on 3.14 or only older? Maybe just switching to latest python already fixes this, if not, maybe starting the forkserver explicitly at a known point (before RSS gets too big) would solve it. |
ProcessPoolExecutor is used for Meson's own tests, while this issue/discussion is about the For the latter, tests are run using |
|
@keith-packard, can you test this? It should make it possible to use vfork instead of fork... diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index 6446f7df2..8982db14f 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -1596,28 +1596,25 @@ class SingleTestRunner:
signal.signal(signal.SIGINT, signal.SIG_IGN)
def preexec_fn() -> None:
- if self.options.interactive:
- # Restore the SIGINT handler for the child process to
- # ensure it can handle it.
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- else:
- # We don't want setsid() in gdb because gdb needs the
- # terminal in order to handle ^C and not show tcsetpgrp()
- # errors avoid not being able to use the terminal.
- os.setsid()
+ # Restore the SIGINT handler for the child process to
+ # ensure it can handle it.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
def postwait_fn() -> None:
if self.options.interactive:
# Let us accept ^C again
signal.signal(signal.SIGINT, previous_sigint_handler)
+ start_new_session = not (is_windows() or is_os2()) and not self.options.interactive
+ need_preexec_fn = not (is_windows() or is_os2()) and self.options.interactive
p = await asyncio.create_subprocess_exec(*args,
stdin=stdin,
stdout=stdout,
stderr=stderr,
env=env,
cwd=cwd,
- preexec_fn=preexec_fn if not (is_windows() or is_os2()) else None)
+ start_new_session=start_new_session,
+ preexec_fn=preexec_fn if need_preexec_fn else None)
return TestSubprocess(p, stdout=stdout, stderr=stderr,
postwait_fn=postwait_fn if not is_windows() else None)
|
|
I finally got around to testing the proposed patch and ... HURRAY! IT WORKS! This reduces testing time by a huge factor. What is needed to get this finished up and merged? |
Uh oh!
There was an error while loading. Please reload this page.
Describe the bug
When there are a lot of tests (thousands), the meson overhead per test is large. In the test case provided with 17576 tests, meson itself consumes over 7 minutes of CPU time, essentially all of which is 'sys' time:
Each test consumes about 25ms of sys time.
Some experimentation with cProfile and strace shows that the 'clone' syscall is taking a large amount of time (20ms or more). As that scales with memory usage, I looked at the virtual address space of the meson process and discovered that while running the test, python is using over 300MB of memory. Running a subset of the tests by adding
--suite areduces the virtual size to about 170MB:Each test in this subset consumes 14ms of sys time, making the overhead scale linearly with virtual address space of the python process.
To Reproduce
Run just a subset of the tests:
Run all of the tests:
Expected behavior
I expect meson to introduce minimal per-test overhead so that testing time is not dominated by meson overhead.
system parameters
meson --versionninja --versionif it's a Ninja buildAll reactions