Skip to content

Commit 562c6da

Browse files
committed
Restore old initial batch distribution logic in LoadScheduling
pytest orders tests for optimal sequential execution - i. e. avoiding unnecessary setup and teardown of fixtures. So executing tests in consecutive chunks is important for optimal performance. Commit 09d79ac optimized test distribution for the corner case, when the number of tests is less than 2 * number of nodes. At the same time, it made initial test distribution worse for all other cases. If some tests use some fixture, and these tests fit into the initial batch, the fixture will be created min(n_tests, n_workers) times, no matter how many other tests there are. With the old algorithm (before 09d79ac), if there are enough tests not using the fixture, the fixture was created only once. So restore the old behavior for typical cases where the number of tests is much greater than the number of workers (or, strictly speaking, when there are at least 2 tests for every node). In my test suite, where fixtures create Docker containers, this change reduces total run time by 10-15%. This is a partial revert of commit 09d79ac
1 parent 9236f11 commit 562c6da

File tree

3 files changed

+20
-13
lines changed

3 files changed

+20
-13
lines changed

changelog/812.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Partially restore old initial batch distribution algorithm in ``LoadScheduling``.

src/xdist/scheduler/load.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,19 @@ def schedule(self):
248248
# Send a batch of tests to run. If we don't have at least two
249249
# tests per node, we have to send them all so that we can send
250250
# shutdown signals and get all nodes working.
251-
initial_batch = max(len(self.pending) // 4, 2 * len(self.nodes))
252-
253-
# distribute tests round-robin up to the batch size
254-
# (or until we run out)
255-
nodes = cycle(self.nodes)
256-
for i in range(initial_batch):
257-
self._send_tests(next(nodes), 1)
251+
if len(self.pending) < 2 * len(self.nodes):
252+
# distribute tests round-robin
253+
nodes = cycle(self.nodes)
254+
for i in range(len(self.pending)):
255+
self._send_tests(next(nodes), 1)
256+
else:
257+
# how many items per node do we have about?
258+
items_per_node = len(self.collection) // len(self.node2pending)
259+
# take a fraction of tests for initial distribution
260+
node_chunksize = max(items_per_node // 4, 2)
261+
# and initialize each node with a chunk of tests
262+
for node in self.nodes:
263+
self._send_tests(node, node_chunksize)
258264

259265
if not self.pending:
260266
# initial distribution sent all tests, start node shutdown

testing/test_dsession.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,18 @@ def test_schedule_batch_size(self, pytester: pytest.Pytester) -> None:
115115
# assert not sched.tests_finished
116116
sent1 = node1.sent
117117
sent2 = node2.sent
118-
assert sent1 == [0, 2]
119-
assert sent2 == [1, 3]
118+
assert sent1 == [0, 1]
119+
assert sent2 == [2, 3]
120120
assert sched.pending == [4, 5]
121121
assert sched.node2pending[node1] == sent1
122122
assert sched.node2pending[node2] == sent2
123123
assert len(sched.pending) == 2
124124
sched.mark_test_complete(node1, 0)
125-
assert node1.sent == [0, 2, 4]
125+
assert node1.sent == [0, 1, 4]
126126
assert sched.pending == [5]
127-
assert node2.sent == [1, 3]
128-
sched.mark_test_complete(node1, 2)
129-
assert node1.sent == [0, 2, 4, 5]
127+
assert node2.sent == [2, 3]
128+
sched.mark_test_complete(node1, 1)
129+
assert node1.sent == [0, 1, 4, 5]
130130
assert not sched.pending
131131

132132
def test_schedule_fewer_tests_than_nodes(self, pytester: pytest.Pytester) -> None:

0 commit comments

Comments
 (0)