Simon Jakobi pushed to branch wip/sjakobi/T27602 at Glasgow Haskell Compiler / GHC

Commits:

3 changed files:

Changes:

  • .gitlab/ci.sh
    ... ... @@ -1120,9 +1120,10 @@ case ${1:-help} in
    1120 1120
       setup) setup && cleanup_submodules ;;
    
    1121 1121
       configure) time_it "configure" configure ;;
    
    1122 1122
       build_hadrian) time_it "build" build_hadrian ;;
    
    1123
    -  # N.B. Always push notes, even if the build fails. This is okay to do as the
    
    1124
    -  # testsuite driver doesn't record notes for tests that fail due to
    
    1125
    -  # correctness.
    
    1123
    +  # N.B. Always push notes, even if the build fails. Metrics from runs failing
    
    1124
    +  # a perf stat check are deliberately recorded too — discarding them would
    
    1125
    +  # bias the baseline towards whichever sample came first. Only correctness
    
    1126
    +  # failures record nothing.
    
    1126 1127
       test_hadrian)
    
    1127 1128
         fetch_perf_notes
    
    1128 1129
         res=0
    

  • testsuite/driver/perf_notes.py
    ... ... @@ -84,8 +84,11 @@ PerfStat = NamedTuple('PerfStat', [('test_env', TestEnv),
    84 84
                                        ('value', float)])
    
    85 85
     
    
    86 86
     # A baseline recovered form stored metrics.
    
    87
    -Baseline = NamedTuple('Baseline', [('perfStat', PerfStat),
    
    88
    -                                   ('commit', GitHash)])
    
    87
    +class Baseline(NamedTuple):
    
    88
    +    perfStat: PerfStat
    
    89
    +    commit: GitHash
    
    90
    +    # The raw samples the baseline value was averaged over.
    
    91
    +    samples: List[float] = []
    
    89 92
     
    
    90 93
     # The type of exceptions which are thrown when computing the current stat value
    
    91 94
     # fails.
    
    ... ... @@ -465,6 +468,10 @@ def get_allowed_changes(baseline_ref: Optional[GitRef]) -> Dict[TestName, List[A
    465 468
     # (bool          , str   ) -> (str     , str , str   , str) -> float
    
    466 469
     _commit_metric_cache = {} # type: ignore
    
    467 470
     
    
    471
    +# Like _commit_metric_cache, but mapping to the list of raw sample values the
    
    472
    +# baseline was averaged over. Filled by get_commit_metric.
    
    473
    +_commit_samples_cache = {} # type: ignore
    
    474
    +
    
    468 475
     # Get the baseline of a test at a given commit. This is the expected value
    
    469 476
     # *before* the commit is applied (i.e. on the parent commit).
    
    470 477
     # This searches git notes from older commits for recorded metrics (locally and
    
    ... ... @@ -506,7 +513,8 @@ def baseline_metric(commit: GitHash,
    506 513
             if baseline_commit is not None:
    
    507 514
                 current_metric = get_commit_metric(namespace, baseline_commit, test_env, name, metric, way)
    
    508 515
                 if current_metric is not None:
    
    509
    -                return Baseline(current_metric, baseline_commit)
    
    516
    +                return Baseline(current_metric, baseline_commit,
    
    517
    +                                get_commit_samples(namespace, baseline_commit, test_env, name, metric, way))
    
    510 518
                 else:
    
    511 519
                     return None
    
    512 520
     
    
    ... ... @@ -515,7 +523,8 @@ def baseline_metric(commit: GitHash,
    515 523
                 # Check for a metric on this commit.
    
    516 524
                 current_metric = get_commit_metric(namespace, current_commit, test_env, name, metric, way)
    
    517 525
                 if current_metric is not None:
    
    518
    -                return Baseline(current_metric, current_commit)
    
    526
    +                return Baseline(current_metric, current_commit,
    
    527
    +                                get_commit_samples(namespace, current_commit, test_env, name, metric, way))
    
    519 528
     
    
    520 529
                 # Stop if there is an expected change at this commit. In that case
    
    521 530
                 # metrics on ancestor commits will not be a valid baseline.
    
    ... ... @@ -598,8 +607,26 @@ def get_commit_metric(gitNoteRef,
    598 607
     
    
    599 608
         # Save baselines to the cache.
    
    600 609
         _commit_metric_cache[cacheKeyA] = baseline_by_cache_key_b
    
    610
    +    _commit_samples_cache[cacheKeyA] = values_by_cache_key_b
    
    601 611
         return baseline_by_cache_key_b.get(cacheKeyB)
    
    602 612
     
    
    613
    +# Get the raw sample values that get_commit_metric averages over. Uses the
    
    614
    +# cache filled by get_commit_metric, so no extra git calls after it has run.
    
    615
    +def get_commit_samples(gitNoteRef,
    
    616
    +                       ref: Union[GitRef, GitHash],
    
    617
    +                       test_env: TestEnv,
    
    618
    +                       name: TestName,
    
    619
    +                       metric: MetricName,
    
    620
    +                       way: WayName
    
    621
    +                       ) -> List[float]:
    
    622
    +    get_commit_metric(gitNoteRef, ref, test_env, name, metric, way)
    
    623
    +    cacheKeyA = (gitNoteRef, commit_hash(ref))
    
    624
    +    cacheKeyB = (test_env, name, metric, way)
    
    625
    +    return _commit_samples_cache.get(cacheKeyA, {}).get(cacheKeyB, [])
    
    626
    +
    
    627
    +def format_samples(samples: List[float]) -> str:
    
    628
    +    return ', '.join(str(int(s)) if s == int(s) else str(s) for s in samples)
    
    629
    +
    
    603 630
     def check_stats_change(actual: PerfStat,
    
    604 631
                            baseline: Baseline,
    
    605 632
                            acceptance_window: MetricAcceptanceWindow,
    
    ... ... @@ -654,9 +681,16 @@ def check_stats_change(actual: PerfStat,
    654 681
                     ' baseline @ %s' % baseline.commit
    
    655 682
             print(actual.metric, error + ':')
    
    656 683
             dev = 100.0 if expected_val == 0 else round(((float(actual.value) * 100) / int(expected_val)) - 100, 1)
    
    684
    +        # Show the samples so outliers become visible (#27602).
    
    685
    +        if len(baseline.samples) > 1:
    
    686
    +            samples_note = ('; baseline is mean of %d samples: %s'
    
    687
    +                            % (len(baseline.samples),
    
    688
    +                               format_samples(baseline.samples)))
    
    689
    +        else:
    
    690
    +            samples_note = ''
    
    657 691
             change_line = (f'{actual.metric} {change.value} from {baseline.perfStat.test_env} '
    
    658 692
                            f'baseline @ {baseline.commit[:7]}: {expected_val} -> {actual.value} '
    
    659
    -                       f'({dev:+g}%, allowed {acceptance_window.describe()})')
    
    693
    +                       f'({dev:+g}%, allowed {acceptance_window.describe()}{samples_note})')
    
    660 694
             result = failBecause('stat ' + change_line, tag='stat')
    
    661 695
     
    
    662 696
         if not change_allowed or force_print:
    
    ... ... @@ -666,6 +700,10 @@ def check_stats_change(actual: PerfStat,
    666 700
                 print(descr, str(val).rjust(length), extra)
    
    667 701
     
    
    668 702
             display('    Expected    ' + full_name + ' ' + actual.metric + ':', expected_val, acceptance_window.describe())
    
    703
    +        if len(baseline.samples) > 1:
    
    704
    +            display('    Samples     ' + full_name + ' ' + actual.metric + ':',
    
    705
    +                    len(baseline.samples),
    
    706
    +                    '(' + format_samples(baseline.samples) + ')')
    
    669 707
             display('    Lower bound ' + full_name + ' ' + actual.metric + ':', lowerBound, '')
    
    670 708
             display('    Upper bound ' + full_name + ' ' + actual.metric + ':', upperBound, '')
    
    671 709
             display('    Actual      ' + full_name + ' ' + actual.metric + ':', actual.value, '')
    

  • testsuite/tests/perf/compiler/all.T
    ... ... @@ -48,7 +48,9 @@ test('T4801',
    48 48
          [''])
    
    49 49
     
    
    50 50
     test('T3064',
    
    51
    -     [collect_compiler_residency(20),
    
    51
    +     [# Demo-only: tightened from 20 so the bimodal max_bytes_used
    
    52
    +      # baseline on aarch64-linux-deb13-validate fails.
    
    53
    +      collect_compiler_residency(1),
    
    52 54
           collect_compiler_runtime(2),
    
    53 55
           only_ways(['normal']),
    
    54 56
           ],