Skip to content

Commit 176a89f

Browse files
authored
Rollup merge of rust-lang#103689 - saethlin:libtest-startup, r=thomcc
Do fewer passes and generally be more efficient when filtering tests Follow-on of the work I started with this PR: rust-lang#99939 Basically, the startup code for libtest is really inefficient, but that's not usually a problem because it is distributed in release and workloads are small. But under Miri which can be 100x slower than a debug build, these inefficiencies explode. Most of the diff here is making test filtering single-pass. There are a few other small optimizations as well, but they are more straightforward. With this PR, the startup time of the `iced` tests with `--features=code_asm,mvex` drops from 17 to 2 minutes (I think Miri has gotten slower under this workload since rust-lang#99939). The easiest way to try this out is to set `MIRI_LIB_SRC` to a checkout of this branch when running `cargo +nightly miri test --features=code_asm,mvex`. r? `@thomcc`
2 parents 8564ee8 + 17b86cb commit 176a89f

File tree

3 files changed

+66
-29
lines changed

3 files changed

+66
-29
lines changed

library/test/src/console.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ fn on_test_event(
228228
out: &mut dyn OutputFormatter,
229229
) -> io::Result<()> {
230230
match (*event).clone() {
231-
TestEvent::TeFiltered(ref filtered_tests, shuffle_seed) => {
232-
st.total = filtered_tests.len();
233-
out.write_run_start(filtered_tests.len(), shuffle_seed)?;
231+
TestEvent::TeFiltered(filtered_tests, shuffle_seed) => {
232+
st.total = filtered_tests;
233+
out.write_run_start(filtered_tests, shuffle_seed)?;
234234
}
235235
TestEvent::TeFilteredOut(filtered_out) => {
236236
st.filtered_out = filtered_out;

library/test/src/event.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl CompletedTest {
2828

2929
#[derive(Debug, Clone)]
3030
pub enum TestEvent {
31-
TeFiltered(Vec<TestDesc>, Option<u64>),
31+
TeFiltered(usize, Option<u64>),
3232
TeWait(TestDesc),
3333
TeResult(CompletedTest),
3434
TeTimeout(TestDesc),

library/test/src/lib.rs

+62-25
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,35 @@ pub fn assert_test_result<T: Termination>(result: T) -> Result<(), String> {
219219
}
220220
}
221221

222+
struct FilteredTests {
223+
tests: Vec<(TestId, TestDescAndFn)>,
224+
benchs: Vec<(TestId, TestDescAndFn)>,
225+
next_id: usize,
226+
}
227+
228+
impl FilteredTests {
229+
fn add_bench(&mut self, desc: TestDesc, testfn: TestFn) {
230+
let test = TestDescAndFn { desc, testfn };
231+
self.benchs.push((TestId(self.next_id), test));
232+
self.next_id += 1;
233+
}
234+
fn add_test(&mut self, desc: TestDesc, testfn: TestFn) {
235+
let test = TestDescAndFn { desc, testfn };
236+
self.tests.push((TestId(self.next_id), test));
237+
self.next_id += 1;
238+
}
239+
fn add_bench_as_test(
240+
&mut self,
241+
desc: TestDesc,
242+
benchfn: impl Fn(&mut Bencher) -> Result<(), String> + Send + 'static,
243+
) {
244+
let testfn = DynTestFn(Box::new(move || {
245+
bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b)))
246+
}));
247+
self.add_test(desc, testfn);
248+
}
249+
}
250+
222251
pub fn run_tests<F>(
223252
opts: &TestOpts,
224253
tests: Vec<TestDescAndFn>,
@@ -247,45 +276,51 @@ where
247276

248277
let tests_len = tests.len();
249278

250-
let mut filtered_tests = filter_tests(opts, tests);
251-
if !opts.bench_benchmarks {
252-
filtered_tests = convert_benchmarks_to_tests(filtered_tests);
253-
}
279+
let mut filtered = FilteredTests { tests: Vec::new(), benchs: Vec::new(), next_id: 0 };
254280

255-
let filtered_tests = {
256-
let mut filtered_tests = filtered_tests;
257-
for test in filtered_tests.iter_mut() {
258-
test.desc.name = test.desc.name.with_padding(test.testfn.padding());
259-
}
281+
for test in filter_tests(opts, tests) {
282+
let mut desc = test.desc;
283+
desc.name = desc.name.with_padding(test.testfn.padding());
260284

261-
filtered_tests
262-
};
285+
match test.testfn {
286+
DynBenchFn(benchfn) => {
287+
if opts.bench_benchmarks {
288+
filtered.add_bench(desc, DynBenchFn(benchfn));
289+
} else {
290+
filtered.add_bench_as_test(desc, benchfn);
291+
}
292+
}
293+
StaticBenchFn(benchfn) => {
294+
if opts.bench_benchmarks {
295+
filtered.add_bench(desc, StaticBenchFn(benchfn));
296+
} else {
297+
filtered.add_bench_as_test(desc, benchfn);
298+
}
299+
}
300+
testfn => {
301+
filtered.add_test(desc, testfn);
302+
}
303+
};
304+
}
263305

264-
let filtered_out = tests_len - filtered_tests.len();
306+
let filtered_out = tests_len - filtered.tests.len();
265307
let event = TestEvent::TeFilteredOut(filtered_out);
266308
notify_about_test_event(event)?;
267309

268-
let filtered_descs = filtered_tests.iter().map(|t| t.desc.clone()).collect();
269-
270310
let shuffle_seed = get_shuffle_seed(opts);
271311

272-
let event = TestEvent::TeFiltered(filtered_descs, shuffle_seed);
312+
let event = TestEvent::TeFiltered(filtered.tests.len(), shuffle_seed);
273313
notify_about_test_event(event)?;
274314

275-
let (mut filtered_tests, filtered_benchs): (Vec<_>, _) = filtered_tests
276-
.into_iter()
277-
.enumerate()
278-
.map(|(i, e)| (TestId(i), e))
279-
.partition(|(_, e)| matches!(e.testfn, StaticTestFn(_) | DynTestFn(_)));
280-
281315
let concurrency = opts.test_threads.unwrap_or_else(get_concurrency);
282316

317+
let mut remaining = filtered.tests;
283318
if let Some(shuffle_seed) = shuffle_seed {
284-
shuffle_tests(shuffle_seed, &mut filtered_tests);
319+
shuffle_tests(shuffle_seed, &mut remaining);
285320
}
286321
// Store the tests in a VecDeque so we can efficiently remove the first element to run the
287322
// tests in the order they were passed (unless shuffled).
288-
let mut remaining = VecDeque::from(filtered_tests);
323+
let mut remaining = VecDeque::from(remaining);
289324
let mut pending = 0;
290325

291326
let (tx, rx) = channel::<CompletedTest>();
@@ -402,7 +437,7 @@ where
402437

403438
if opts.bench_benchmarks {
404439
// All benchmarks run at the end, in serial.
405-
for (id, b) in filtered_benchs {
440+
for (id, b) in filtered.benchs {
406441
let event = TestEvent::TeWait(b.desc.clone());
407442
notify_about_test_event(event)?;
408443
run_test(opts, false, id, b, run_strategy, tx.clone(), Concurrent::No);
@@ -432,7 +467,9 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
432467
}
433468

434469
// Skip tests that match any of the skip filters
435-
filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
470+
if !opts.skip.is_empty() {
471+
filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
472+
}
436473

437474
// Excludes #[should_panic] tests
438475
if opts.exclude_should_panic {

0 commit comments

Comments
 (0)