@@ -8,6 +8,7 @@ use crate::compile::execute::{CargoProcess, Processor};
8
8
use crate :: toolchain:: Toolchain ;
9
9
use crate :: utils:: wait_for_future;
10
10
use anyhow:: { bail, Context } ;
11
+ use database:: selector:: CompileTestCase ;
11
12
use log:: debug;
12
13
use std:: collections:: { HashMap , HashSet } ;
13
14
use std:: fmt:: { Display , Formatter } ;
@@ -243,6 +244,7 @@ impl Benchmark {
243
244
toolchain : & Toolchain ,
244
245
iterations : Option < usize > ,
245
246
targets : & [ Target ] ,
247
+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
246
248
) -> anyhow:: Result < ( ) > {
247
249
if self . config . disabled {
248
250
eprintln ! ( "Skipping {}: disabled" , self . name) ;
@@ -273,19 +275,65 @@ impl Benchmark {
273
275
return Ok ( ( ) ) ;
274
276
}
275
277
276
- eprintln ! ( "Preparing {}" , self . name) ;
277
- let mut target_dirs: Vec < ( ( CodegenBackend , Profile , Target ) , TempDir ) > = vec ! [ ] ;
278
+ struct BenchmarkDir {
279
+ dir : TempDir ,
280
+ scenarios : Vec < Scenario > ,
281
+ profile : Profile ,
282
+ backend : CodegenBackend ,
283
+ target : Target ,
284
+ }
285
+
286
+ // Materialize the test cases that we want to benchmark
287
+ // We need to handle scenarios a bit specially, because they share the target directory
288
+ let mut benchmark_dirs: Vec < BenchmarkDir > = vec ! [ ] ;
289
+
278
290
for backend in backends {
279
291
for profile in & profiles {
280
292
for target in targets {
281
- target_dirs. push ( (
282
- ( * backend, * profile, * target) ,
283
- self . make_temp_dir ( & self . path ) ?,
284
- ) ) ;
293
+ // Do we have any scenarios left to compute?
294
+ let remaining_scenarios = scenarios
295
+ . iter ( )
296
+ . filter ( |scenario| {
297
+ self . should_run_scenario (
298
+ scenario,
299
+ profile,
300
+ backend,
301
+ target,
302
+ already_computed,
303
+ )
304
+ } )
305
+ . copied ( )
306
+ . collect :: < Vec < Scenario > > ( ) ;
307
+ if remaining_scenarios. is_empty ( ) {
308
+ continue ;
309
+ }
310
+
311
+ let temp_dir = self . make_temp_dir ( & self . path ) ?;
312
+ benchmark_dirs. push ( BenchmarkDir {
313
+ dir : temp_dir,
314
+ scenarios : remaining_scenarios,
315
+ profile : * profile,
316
+ backend : * backend,
317
+ target : * target,
318
+ } ) ;
285
319
}
286
320
}
287
321
}
288
322
323
+ if benchmark_dirs. is_empty ( ) {
324
+ eprintln ! (
325
+ "Skipping {}: all test cases were previously computed" ,
326
+ self . name
327
+ ) ;
328
+ return Ok ( ( ) ) ;
329
+ }
330
+
331
+ eprintln ! (
332
+ "Preparing {} (test cases: {})" ,
333
+ self . name,
334
+ benchmark_dirs. len( )
335
+ ) ;
336
+
289
337
// In parallel (but with a limit to the number of CPUs), prepare all
290
338
// profiles. This is done in parallel vs. sequentially because:
291
339
// * We don't record any measurements during this phase, so the
@@ -319,18 +367,18 @@ impl Benchmark {
319
367
. get ( ) ,
320
368
)
321
369
. context ( "jobserver::new" ) ?;
322
- let mut threads = Vec :: with_capacity ( target_dirs . len ( ) ) ;
323
- for ( ( backend , profile , target ) , prep_dir ) in & target_dirs {
370
+ let mut threads = Vec :: with_capacity ( benchmark_dirs . len ( ) ) ;
371
+ for benchmark_dir in & benchmark_dirs {
324
372
let server = server. clone ( ) ;
325
373
let thread = s. spawn :: < _ , anyhow:: Result < ( ) > > ( move || {
326
374
wait_for_future ( async move {
327
375
let server = server. clone ( ) ;
328
376
self . mk_cargo_process (
329
377
toolchain,
330
- prep_dir . path ( ) ,
331
- * profile,
332
- * backend,
333
- * target,
378
+ benchmark_dir . dir . path ( ) ,
379
+ benchmark_dir . profile ,
380
+ benchmark_dir . backend ,
381
+ benchmark_dir . target ,
334
382
)
335
383
. jobserver ( server)
336
384
. run_rustc ( false )
@@ -365,10 +413,11 @@ impl Benchmark {
365
413
let mut timing_dirs: Vec < ManuallyDrop < TempDir > > = vec ! [ ] ;
366
414
367
415
let benchmark_start = std:: time:: Instant :: now ( ) ;
368
- for ( ( backend, profile, target) , prep_dir) in & target_dirs {
369
- let backend = * backend;
370
- let profile = * profile;
371
- let target = * target;
416
+ for benchmark_dir in & benchmark_dirs {
417
+ let backend = benchmark_dir. backend ;
418
+ let profile = benchmark_dir. profile ;
419
+ let target = benchmark_dir. target ;
420
+ let scenarios = & benchmark_dir. scenarios ;
372
421
eprintln ! (
373
422
"Running {}: {:?} + {:?} + {:?} + {:?}" ,
374
423
self . name, profile, scenarios, backend, target,
@@ -388,7 +437,7 @@ impl Benchmark {
388
437
}
389
438
log:: debug!( "Benchmark iteration {}/{}" , i + 1 , iterations) ;
390
439
// Don't delete the directory on error.
391
- let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( prep_dir . path ( ) ) ?) ;
440
+ let timing_dir = ManuallyDrop :: new ( self . make_temp_dir ( benchmark_dir . dir . path ( ) ) ?) ;
392
441
let cwd = timing_dir. path ( ) ;
393
442
394
443
// A full non-incremental build.
@@ -458,6 +507,67 @@ impl Benchmark {
458
507
459
508
Ok ( ( ) )
460
509
}
510
+
511
+ /// Return true if the given `scenario` should be computed.
512
+ fn should_run_scenario (
513
+ & self ,
514
+ scenario : & Scenario ,
515
+ profile : & Profile ,
516
+ backend : & CodegenBackend ,
517
+ target : & Target ,
518
+ already_computed : & hashbrown:: HashSet < CompileTestCase > ,
519
+ ) -> bool {
520
+ let benchmark = database:: Benchmark :: from ( self . name . 0 . as_str ( ) ) ;
521
+ let profile = match profile {
522
+ Profile :: Check => database:: Profile :: Check ,
523
+ Profile :: Debug => database:: Profile :: Debug ,
524
+ Profile :: Doc => database:: Profile :: Doc ,
525
+ Profile :: DocJson => database:: Profile :: DocJson ,
526
+ Profile :: Opt => database:: Profile :: Opt ,
527
+ Profile :: Clippy => database:: Profile :: Clippy ,
528
+ } ;
529
+ let backend = match backend {
530
+ CodegenBackend :: Llvm => database:: CodegenBackend :: Llvm ,
531
+ CodegenBackend :: Cranelift => database:: CodegenBackend :: Cranelift ,
532
+ } ;
533
+ let target = match target {
534
+ Target :: X86_64UnknownLinuxGnu => database:: Target :: X86_64UnknownLinuxGnu ,
535
+ } ;
536
+
537
+ match scenario {
538
+ // For these scenarios, we can simply check if they were benchmarked or not
539
+ Scenario :: Full | Scenario :: IncrFull | Scenario :: IncrUnchanged => {
540
+ let test_case = CompileTestCase {
541
+ benchmark,
542
+ profile,
543
+ backend,
544
+ target,
545
+ scenario : match scenario {
546
+ Scenario :: Full => database:: Scenario :: Empty ,
547
+ Scenario :: IncrFull => database:: Scenario :: IncrementalEmpty ,
548
+ Scenario :: IncrUnchanged => database:: Scenario :: IncrementalFresh ,
549
+ Scenario :: IncrPatched => unreachable ! ( ) ,
550
+ } ,
551
+ } ;
552
+ !already_computed. contains ( & test_case)
553
+ }
554
+ // For incr-patched, it is a bit more complicated.
555
+ // If there is at least a single uncomputed `IncrPatched`, we need to rerun
556
+ // all of them, because they stack on top of one another.
557
+ // Note that we don't need to explicitly include `IncrFull` if `IncrPatched`
558
+ // is selected, as the benchmark code will always run `IncrFull` before `IncrPatched`.
559
+ Scenario :: IncrPatched => self . patches . iter ( ) . any ( |patch| {
560
+ let test_case = CompileTestCase {
561
+ benchmark,
562
+ profile,
563
+ scenario : database:: Scenario :: IncrementalPatch ( patch. name ) ,
564
+ backend,
565
+ target,
566
+ } ;
567
+ !already_computed. contains ( & test_case)
568
+ } ) ,
569
+ }
570
+ }
461
571
}
462
572
463
573
/// Directory containing compile-time benchmarks.
0 commit comments