@@ -8,6 +8,7 @@ use crate::compile::execute::{CargoProcess, Processor};
88use crate :: toolchain:: Toolchain ;
99use crate :: utils:: wait_for_future;
1010use anyhow:: { bail, Context } ;
11+ use database:: selector:: CompileTestCase ;
1112use log:: debug;
1213use std:: collections:: { HashMap , HashSet } ;
1314use std:: fmt:: { Display , Formatter } ;
@@ -249,6 +250,7 @@ impl Benchmark {
249250 toolchain : & Toolchain ,
250251 iterations : Option < usize > ,
251252 targets : & [ Target ] ,
253+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
252254 ) -> anyhow:: Result < ( ) > {
253255 if self . config . disabled {
254256 eprintln ! ( "Skipping {}: disabled" , self . name) ;
@@ -279,19 +281,61 @@ impl Benchmark {
279281 return Ok ( ( ) ) ;
280282 }
281283
282- eprintln ! ( "Preparing {}" , self . name) ;
283- let mut target_dirs: Vec < ( ( CodegenBackend , Profile , Target ) , TempDir ) > = vec ! [ ] ;
284+ struct BenchmarkDir {
285+ dir : TempDir ,
286+ scenarios : Vec < Scenario > ,
287+ profile : Profile ,
288+ backend : CodegenBackend ,
289+ target : Target ,
290+ }
291+
292+ // Materialize the test cases that we want to benchmark
293+ // We need to handle scenarios a bit specially, because they share the target directory
294+ let mut benchmark_dirs: Vec < BenchmarkDir > = vec ! [ ] ;
295+
284296 for backend in backends {
285297 for profile in & profiles {
286298 for target in targets {
287- target_dirs. push ( (
288- ( * backend, * profile, * target) ,
289- self . make_temp_dir ( & self . path ) ?,
290- ) ) ;
299+ // Do we have any scenarios left to compute?
300+ let remaining_scenarios = scenarios
301+ . iter ( )
302+ . filter ( |scenario| {
303+ self . should_run_scenario (
304+ scenario,
305+ profile,
306+ backend,
307+ target,
308+ already_computed,
309+ )
310+ } )
311+ . copied ( )
312+ . collect :: < Vec < Scenario > > ( ) ;
313+ if remaining_scenarios. is_empty ( ) {
314+ continue ;
315+ }
316+
317+ let temp_dir = self . make_temp_dir ( & self . path ) ?;
318+ benchmark_dirs. push ( BenchmarkDir {
319+ dir : temp_dir,
320+ scenarios : remaining_scenarios,
321+ profile : * profile,
322+ backend : * backend,
323+ target : * target,
324+ } ) ;
291325 }
292326 }
293327 }
294328
329+ if benchmark_dirs. is_empty ( ) {
330+ eprintln ! (
331+ "Skipping {}: all test cases were previously computed" ,
332+ self . name
333+ ) ;
334+ return Ok ( ( ) ) ;
335+ }
336+
337+ eprintln ! ( "Preparing {}" , self . name) ;
338+
295339 // In parallel (but with a limit to the number of CPUs), prepare all
296340 // profiles. This is done in parallel vs. sequentially because:
297341 // * We don't record any measurements during this phase, so the
@@ -325,18 +369,18 @@ impl Benchmark {
325369 . get ( ) ,
326370 )
327371 . context ( "jobserver::new" ) ?;
328- let mut threads = Vec :: with_capacity ( target_dirs . len ( ) ) ;
329- for ( ( backend , profile , target ) , prep_dir ) in & target_dirs {
372+ let mut threads = Vec :: with_capacity ( benchmark_dirs . len ( ) ) ;
373+ for benchmark_dir in & benchmark_dirs {
330374 let server = server. clone ( ) ;
331375 let thread = s. spawn :: < _ , anyhow:: Result < ( ) > > ( move || {
332376 wait_for_future ( async move {
333377 let server = server. clone ( ) ;
334378 self . mk_cargo_process (
335379 toolchain,
336- prep_dir . path ( ) ,
337- * profile,
338- * backend,
339- * target,
380+ benchmark_dir . dir . path ( ) ,
381+ benchmark_dir . profile ,
382+ benchmark_dir . backend ,
383+ benchmark_dir . target ,
340384 )
341385 . jobserver ( server)
342386 . run_rustc ( false )
@@ -371,10 +415,11 @@ impl Benchmark {
371415 let mut timing_dirs: Vec < ManuallyDrop < TempDir > > = vec ! [ ] ;
372416
373417 let benchmark_start = std:: time:: Instant :: now ( ) ;
374- for ( ( backend, profile, target) , prep_dir) in & target_dirs {
375- let backend = * backend;
376- let profile = * profile;
377- let target = * target;
418+ for benchmark_dir in & benchmark_dirs {
419+ let backend = benchmark_dir. backend ;
420+ let profile = benchmark_dir. profile ;
421+ let target = benchmark_dir. target ;
422+ let scenarios = & benchmark_dir. scenarios ;
378423 eprintln ! (
379424 "Running {}: {:?} + {:?} + {:?} + {:?}" ,
380425 self . name, profile, scenarios, backend, target,
@@ -394,7 +439,7 @@ impl Benchmark {
394439 }
395440 log:: debug!( "Benchmark iteration {}/{}" , i + 1 , iterations) ;
396441 // Don't delete the directory on error.
397- let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( prep_dir . path ( ) ) ?) ;
442+ let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( benchmark_dir . dir . path ( ) ) ?) ;
398443 let cwd = timing_dir. path ( ) ;
399444
400445 // A full non-incremental build.
@@ -407,7 +452,7 @@ impl Benchmark {
407452
408453 // Rustdoc does not support incremental compilation
409454 if !profile. is_doc ( ) {
410- // An incremental from scratch (slowest incremental case).
455+ // An incremental build from scratch (slowest incremental case).
411456 // This is required for any subsequent incremental builds.
412457 if scenarios. iter ( ) . any ( |s| s. is_incr ( ) ) {
413458 self . mk_cargo_process ( toolchain, cwd, profile, backend, target)
@@ -464,6 +509,60 @@ impl Benchmark {
464509
465510 Ok ( ( ) )
466511 }
512+
513+ /// Return true if the given `scenario` should be computed.
514+ fn should_run_scenario (
515+ & self ,
516+ scenario : & Scenario ,
517+ profile : & Profile ,
518+ backend : & CodegenBackend ,
519+ target : & Target ,
520+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
521+ ) -> bool {
522+ // Keep this in sync with the logic in `Benchmark::measure`.
523+ if scenario. is_incr ( ) && profile. is_doc ( ) {
524+ return false ;
525+ }
526+
527+ let benchmark = database:: Benchmark :: from ( self . name . 0 . as_str ( ) ) ;
528+ let profile: database:: Profile = ( * profile) . into ( ) ;
529+ let backend: database:: CodegenBackend = ( * backend) . into ( ) ;
530+ let target: database:: Target = ( * target) . into ( ) ;
531+
532+ match scenario {
533+ // For these scenarios, we can simply check if they were benchmarked or not
534+ Scenario :: Full | Scenario :: IncrFull | Scenario :: IncrUnchanged => {
535+ let test_case = CompileTestCase {
536+ benchmark,
537+ profile,
538+ backend,
539+ target,
540+ scenario : match scenario {
541+ Scenario :: Full => database:: Scenario :: Empty ,
542+ Scenario :: IncrFull => database:: Scenario :: IncrementalEmpty ,
543+ Scenario :: IncrUnchanged => database:: Scenario :: IncrementalFresh ,
544+ Scenario :: IncrPatched => unreachable ! ( ) ,
545+ } ,
546+ } ;
547+ !already_computed. contains ( & test_case)
548+ }
549+ // For incr-patched, it is a bit more complicated.
550+ // If there is at least a single uncomputed `IncrPatched`, we need to rerun
551+ // all of them, because they stack on top of one another.
552+ // Note that we don't need to explicitly include `IncrFull` if `IncrPatched`
553+ // is selected, as the benchmark code will always run `IncrFull` before `IncrPatched`.
554+ Scenario :: IncrPatched => self . patches . iter ( ) . any ( |patch| {
555+ let test_case = CompileTestCase {
556+ benchmark,
557+ profile,
558+ scenario : database:: Scenario :: IncrementalPatch ( patch. name ) ,
559+ backend,
560+ target,
561+ } ;
562+ !already_computed. contains ( & test_case)
563+ } ) ,
564+ }
565+ }
467566}
468567
469568/// Directory containing compile-time benchmarks.
0 commit comments