Repository navigation
Dynamic PGO Microbenchmark Regressions #87194
Description
This issue tracks investigation into microbenchmarks that have reported regressions with Dynamic PGO enabled. It is a continuation of #84264 which tracked regressions from PGO before it was enabled.
The report below is collated from the following autofiling reports.
- [Perf] Windows/arm64: 67 Regressions on 5/19/2023 1:23:34 PM perf-autofiling-issues#17979
- [Perf] Linux/arm64: 62 Regressions on 5/19/2023 1:23:34 PM perf-autofiling-issues#17982
- [Perf] Windows/x64: 100 Regressions on 5/19/2023 3:32:16 PM perf-autofiling-issues#17994
- [Perf] Windows/arm64: 61 Regressions on 5/19/2023 1:23:34 PM perf-autofiling-issues#18096
- [Perf] Windows/arm64: 7 Regressions on 5/19/2023 9:46:56 PM perf-autofiling-issues#18097
- [Perf] Linux/arm64: 81 Regressions on 5/19/2023 1:23:34 PM perf-autofiling-issues#18103
- [Perf] Windows/arm64: 4 Regressions on 5/19/2023 1:23:34 PM perf-autofiling-issues#18109
- [Perf] Windows/arm64: 9 Regressions on 5/20/2023 3:57:43 PM perf-autofiling-issues#18111
- [Perf] Linux/x64: 100 Regressions on 5/19/2023 3:32:16 PM perf-autofiling-issues#18139
- [Perf] Windows/x64: 122 Regressions on 5/19/2023 3:32:16 PM perf-autofiling-issues#18151
- [Perf] Windows/x64: 55 Regressions on 5/19/2023 3:32:16 PM perf-autofiling-issues#18582
The table is auto generated by a tool written by @EgorBo but may be edited by hand as regression analysis produces results. The "Score" is the geomean regression across all architectures; benchmarks that did not regress (or get reported) on some architectures are assumed to have produced the same results with and without PGO. "Recent Score" is the current performance (as of 2023-0606) versus the non-PGO result; "Orig Score" is based on the results of auto filing. They will differ if benchmark performance has improved or regressed since the auto filing ran (see for example the results for System.Text.Json.Tests.Perf_Get.GetByte, which has improved already).
Only the 36 entries with recent scores >= 1.3 are included; this leaves off approximately 220 more rows with scores between 1.3 or lower. Our plan is to prioritize investigation of these benchmarks initially, as they have the largest aggregate regressions. If time permits, we will regenerate this chart to pick up the impact of any fixes and see how much of the remainder we can tackle.
Each arch/os result is a hyperlink to the performance data graph for that benchmark. ~Note we currently have no autofiling data for win-x64-intel. If/when that shows up we will regenerate the table.~~
[edit: had to regenerate the table once already, as the scoring logic was off]
[edit: have x64 win intel data now, new table. Not current results have shifted so table is somewhat different...]
cc @dotnet/jit-contrib
| Notes | Recent Score | Orig Score | arm64-lin-ampere | arm64-win-surface | arm64-win-ampere | x64-lin-intel | x64-win-intel | x64-win-amd | Benchmark |
|---|---|---|---|---|---|---|---|---|---|
| noise | 3.38 | 1.37 | 3.37 1.36 |
System.Text.RegularExpressions.Tests.Perf_Regex_Industry_RustLang_Sherlock.Count(Pattern: "zqj", Options: None) | |||||
| noise | 3.36 | 1.37 | 3.36 1.37 |
System.Text.RegularExpressions.Tests.Perf_Regex_Industry_RustLang_Sherlock.Count(Pattern: "zqj", Options: NonBacktracking) | |||||
| notes | 2.71 | 3.39 | 2.71 3.39 |
System.Memory.Span(Int32).EndsWith(Size: 4) | |||||
| likely same as above | 2.62 | 3.03 | 2.55 2.27 |
2.59 3.04 |
System.Memory.Span(Int32).SequenceEqual(Size: 4) | ||||
| likely same as above | 1.87 | 1.76 | 1.87 1.76 |
System.Memory.Span(Int32).SequenceCompareToDifferent(Size: 512) | |||||
| (lack of) if conversion | 1.82 | 1.80 | 1.67 1.63 |
1.93 1.92 |
1.86 1.85 |
System.Tests.Perf_Random.NextSingle | |||
| budget | 1.75 | 1.88 | 1.33 1.47 |
1.35 1.49 |
1.90 1.99 |
2.29 2.43 |
2.10 2.19 |
System.Text.Json.Tests.Perf_Get.GetInt16 | |
| BDN | 1.73 | 2.81 | 3.55 3.54 |
1.89 2.00 |
1.28 4.73 |
1.32 2.01 |
1.39 2.68 |
System.Buffers.Text.Tests.Base64EncodeDecodeInPlaceTests.Base64EncodeInPlace(NumberOfBytes: 200000000) | |
| notes | 1.64 | 1.63 | 1.84 1.82 |
1.65 1.64 |
System.Tests.Perf_UInt32.TryParseHex(value: "0") | ||||
| budget | 1.61 | 1.70 | 1.27 1.44 |
1.28 1.46 |
1.24 1.18 |
2.09 2.17 |
2.25 2.33 |
1.86 1.94 |
System.Text.Json.Tests.Perf_Get.GetSByte |
| bimodal | 1.61 | 1.59 | 1.60 1.58 |
System.Text.RegularExpressions.Tests.Perf_Regex_Industry_RustLang_Sherlock.Count(Pattern: "Sherlock Holmes", Options: Compiled) | |||||
| cast expansion | 1.60 | 1.64 | 1.82 1.87 |
1.41 1.43 |
System.Buffers.Tests.ReadOnlySequenceTests(Char).FirstSingleSegment | ||||
| cast expansion | 1.58 | 1.62 | 1.58 1.62 |
System.Buffers.Tests.ReadOnlySequenceTests(Byte).FirstSpanTenSegments | |||||
| cast expansion | 1.52 | 1.65 | 1.48 1.81 |
1.56 1.50 |
System.Buffers.Tests.ReadOnlySequenceTests(Byte).FirstSingleSegment | ||||
| cast expansion | 1.50 | 1.73 | 1.88 2.13 |
1.20 1.41 |
System.Buffers.Tests.ReadOnlySequenceTests(Char).FirstTenSegments | ||||
| likely same as span cases above | 1.48 | 1.28 | 1.48 1.28 |
System.Memory.Span(Int32).Reverse(Size: 4) | |||||
| cast expansion | 1.47 | 1.44 | 1.47 1.44 |
System.Buffers.Tests.ReadOnlySequenceTests(Byte).FirstSpanSingleSegment | |||||
| notes | 1.47 | 1.42 | 1.46 1.42 |
Benchstone.BenchF.InvMt.Test | |||||
| unclear | 1.46 | 1.15 | 1.46 1.15 |
MicroBenchmarks.Serializers.Json_FromStream(MyEventsListerViewModel).DataContractJsonSerializer_ | |||||
| fixed itself | 1.45 | 1.09 | 1.45 1.09 |
System.Tests.Perf_Uri.EscapeDataString(input: "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ | |||||
| unclear | 1.44 | 1.44 | 1.44 1.44 |
Burgers.Test1 | |||||
| unclear | 1.43 | 1.27 | 1.43 1.27 |
System.Text.Json.Document.Tests.Perf_EnumerateArray.EnumerateUsingIndexer(TestCase: ArrayOfNumbers) | |||||
| unclear, linux arm64 only | 1.41 | 1.58 | 1.41 1.58 |
System.Text.Tests.Perf_StringBuilder.Append_Char_Capacity(length: 100000) | |||||
| unclear, linux arm64 only | 1.39 | 1.62 | 1.39 1.62 |
BenchmarksGame.RegexRedux_5.RunBench(options: Compiled) | |||||
| bimodal | 1.39 | 1.39 | 1.39 1.39 |
System.MathBenchmarks.Single.Min | |||||
| bimodal | 1.39 | 1.39 | 1.39 1.39 |
System.MathBenchmarks.Single.Max | |||||
| unclear, linux arm64 only | 1.39 | 1.32 | 1.39 1.32 |
System.IO.Pipes.Tests.Perf_NamedPipeStream.ReadWriteAsync(size: 1000000, Options: Asynchronous) | |||||
| noise | 1.38 | 1.29 | 1.38 1.29 |
System.IO.MemoryMappedFiles.Tests.Perf_MemoryMappedFile.CreateFromFile_Read(capacity: 10000000) | |||||
| bimodal | 1.37 | 1.37 | 1.37 1.37 |
System.Text.RegularExpressions.Tests.Perf_Regex_Industry_RustLang_Sherlock.Count(Pattern: "zqj", Options: Compiled) | |||||
| notes | 1.37 | 1.36 | 1.26 1.29 |
1.42 1.43 |
1.24 1.28 |
1.60 1.48 |
System.Collections.Sort(IntStruct).Array(Size: 512) | ||
| budget | 1.36 | 1.93 | 1.15 1.56 |
1.15 1.58 |
1.27 1.66 |
1.42 2.14 |
1.80 2.67 |
1.49 2.24 |
System.Text.Json.Tests.Perf_Get.GetByte |
| noise | 1.35 | 1.31 | 1.36 1.33 |
System.Memory.Span(Char).IndexOfAnyTwoValues(Size: 512) | |||||
| arm64 only; ldar vs dmb | 1.35 | 1.36 | 1.35 1.34 |
1.38 1.40 |
System.Collections.CtorFromCollection(Int32).ConcurrentBag(Size: 512) | ||||
| fixed by physical promotion | 1.35 | 1.36 | 1.35 1.36 |
Devirtualization.EqualityComparer.ValueTupleCompareWrapped | |||||
| budget | 1.34 | 1.42 | 1.42 1.26 |
1.28 1.38 |
1.35 1.44 |
1.35 1.42 |
1.35 1.55 |
1.31 1.46 |
System.Text.Json.Serialization.Tests.WriteJson(ImmutableDictionary(String, String)).SerializeToStream(Mode: SourceGen) |
| notes | 1.34 | 1.45 | 1.18 1.29 |
1.40 1.44 |
1.13 1.41 |
1.71 1.71 |
System.Collections.Sort(IntStruct).List(Size: 512) | ||
| notes | 1.33 | 1.33 | 1.33 1.33 |
System.Tests.Perf_HashCode.Combine_1 | |||||
| inlining different; exposed local | 1.33 | 1.32 | 1.34 1.33 |
1.32 1.32 |
System.Memory.ReadOnlySequence.Slice_Repeat(Segment: Multiple) | ||||
| notes | 1.33 | 1.18 | 1.33 1.18 |
System.Text.Json.Document.Tests.Perf_EnumerateArray.EnumerateUsingIndexer(TestCase: ArrayOfStrings) | |||||
| budget | 1.32 | 1.37 | 1.24 1.39 |
1.20 1.28 |
1.37 1.15 |
1.39 1.46 |
1.45 1.57 |
1.27 1.39 |
System.Text.Json.Serialization.Tests.WriteJson(ImmutableDictionary(String, String)).SerializeToWriter(Mode: SourceGen) |
| budget | 1.32 | 1.39 | 1.37 1.28 |
1.22 1.42 |
1.34 1.31 |
1.32 1.38 |
1.30 1.50 |
1.34 1.38 |
System.Text.Json.Serialization.Tests.WriteJson(ImmutableDictionary(String, String)).SerializeToUtf8Bytes(Mode: SourceGen) |
| budget | 1.31 | 1.88 | 1.15 1.59 |
1.18 1.62 |
1.03 1.37 |
1.49 2.22 |
1.66 2.49 |
1.49 2.24 |
System.Text.Json.Tests.Perf_Get.GetUInt16 |
| budget | 1.31 | 1.33 | 1.38 1.25 |
1.20 1.23 |
1.23 1.26 |
1.35 1.46 |
1.40 1.40 |
1.41 1.43 |
System.Text.Json.Serialization.Tests.WriteJson(ImmutableDictionary(String, String)).SerializeToString(Mode: SourceGen) |
| jcc errata | 1.31 | 1.39 | 1.31 1.39 |
Span.Sorting.QuickSortSpan(Size: 512) | |||||
| lack of cold inline exposes local | 1.31 | 1.29 | 1.31 1.31 |
1.31 1.27 |
System.Memory.ReadOnlySequence.Slice_Start_And_Length(Segment: Multiple) | ||||
| budget | 1.31 | 1.39 | 1.32 1.19 |
1.20 1.50 |
1.31 1.37 |
1.40 1.50 |
1.31 1.34 |
System.Text.Json.Serialization.Tests.WriteJson(ImmutableDictionary(String, String)).SerializeObjectProperty(Mode: SourceGen) | |
lack of ldapr |
1.30 | 1.30 | 1.29 1.30 |
1.30 1.30 |
System.Collections.CtorFromCollection(String).ConcurrentBag(Size: 512) |
Activity
AndyAyersMS commentedon Jun 6, 2023
System.Buffers.Text.Tests.Base64EncodeDecodeInPlaceTests.Base64EncodeInPlace(NumberOfBytes: 200000000)
System.Buffers.Text.Tests.Base64EncodeDecodeInPlaceTests.Base64DecodeInPlace(NumberOfBytes: 200000000)
These benchmarks were analyzed before PGO was enabled: #84264 (comment)
BDN's strategy doesn't run the benchmark enough, because each iteration is long running, and so (since the key benchmark methods are R2R'd) the test ends up measuring tier1-instr code.
AndyAyersMS commentedon Jun 6, 2023
System.Text.Json.Tests.Perf_Get.GetInt16
System.Text.Json.Tests.Perf_Get.GetSByte
System.Text.Json.Tests.Perf_Get.GetByte
Likely the analysis from #84264 (comment) is still relevant and explains the related tests regressions as well: we run out of inlining budget, in part because the benchmark method is small and there are quite a few large aggressive inline methods, and so we're unable to do some key inlines.
Tracking issue for this is #85531.
Some of these improved with #86551.
AndyAyersMS commentedon Jun 6, 2023
System.Tests.Perf_UInt32.TryParseHex(value: "0")
This regresses across the board, so not sure why we don't have more autofiling for it.
I can't repro on win-x64
But I can (perhaps) on win-arm64 (volterra)
Note the very high overhead.
WorkoadActionUnrollshould have theAggressiveOptimizationattribute so its codegen does not vary.PGO codegen for
TryParseBinaryIntegerHexOrBinaryNumberStylelooks good, all the hot code is adjacent and the method is straight line code. So not clear why it is ~7% or so slower.Path lengths are similar but PGO does one extra STP/LDP and a few more register arg moves. So perhaps that's the explanation?
AndyAyersMS commentedon Jun 6, 2023
System.Memory.Span<Int32>.EndsWith(Size: 4)
System.Memory.Span.SequenceEqual(Size: 4)
These two appear to be arm64 specific.
So issue appears to be in
SequenceEqual?Seems like PGO and non-PGO codegen is the same. Method is R2R'd and with PGO we do a tier1 instr, but do not add any probes. Explanation: this method is an intrinsic and not on the whitelist.
Fixing that (hack) gives:
and more broady
But does not explain why there is a PGO regression. And as you can see the Size=4 results are not very stable.
Similar diffs for
EndsWith(better layout, slightly higher prolog/epilog costs).AndyAyersMS commentedon Jun 6, 2023
System.Memory.Span<Int32>.SequenceCompareToDifferent(Size: 512)
Also seems to be arm64 specific.
AndyAyersMS commentedon Jul 5, 2023
System.Text.RegularExpressions.Tests.Perf_Regex_Industry_RustLang_Sherlock.Count(Pattern: "zqj", Options: None)
These spiked up but then recovered and match their longer-term behavior
25 remaining items
AndyAyersMS commentedon Jul 28, 2023
System.Collections.CtorFromCollection<String>.ConcurrentBag(Size: 512)
arm64 only
Repros on my volterra:
BenchmarkDotNet v0.13.7-nightly.20230724.45, Windows 11 (10.0.22621.2070/22H2/2022Update/SunValley2)
Snapdragon 8cx Gen 3 3.0 GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.100-preview.6.23330.14
[Host] : .NET 8.0.0 (8.0.23.32907), Arm64 RyuJIT AdvSIMD
Job-EGKPHU : .NET 8.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
Job-FFUPKC : .NET 8.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:EnableUnsafeBinaryFormatterSerialization=true IterationTime=250.0000 ms
MaxIterationCount=20 MinIterationCount=15 WarmupCount=1
So issue is evidently in
LocalPush.In the base (no-pgo) jit we form a CSE and this lets us use
ldar, in the diff (pgo) jit we don't do the cse and end up emitting a separate barrier. That may be the culprit.In base there are just two sampling hot spots, both tied to
ldapr:In diff there are more hot spots, and all hottest samples are near the
dmbs.@EgorBo example we were chatting about.
adamsitnik commentedon Jul 31, 2023
I took a look at the benchmark source code and it's safe to do it (the benchmark ID won't change, as the
InvocationsPerIterationconst is not an argument or a parameter for this benchmark)AndyAyersMS commentedon Jul 31, 2023
While there are still a few benchmarks where the analysis is unclear, they are isolated to specific OS/ABI combinations. So I'm going to close this out.
DrewScoggins commentedon Aug 24, 2023
Did BubbleSort2 ever get looked at?
DrewScoggins commentedon Aug 24, 2023
adamsitnik commentedon Aug 25, 2023
Please keep in mind that both bubble sort and
IndexOfare heavily dependent on memory alignment, so it can be unrelated to PGO. The easiest way to verify is to allocate an aligned memory using NativeMemory.AlignedAlloc, create a span out of it and try to repro.DrewScoggins commentedon Aug 25, 2023
Makes sense. I am just adding tests here that were not previously included, but regressed over the same commit range as this check-in.
DrewScoggins commentedon Aug 25, 2023
DrewScoggins commentedon Aug 25, 2023
Major instability starting with this commit.
AndyAyersMS commentedon Aug 25, 2023
Also note that bubble sort runs for a very long time, and so likely BDN + lab customization is not reliably measuring the tier1 codegen , but instead some mixture of Tier0, Tier0 + instrumentation, OSR, and or R2R code.
AndyAyersMS commentedon Aug 25, 2023
Feel free to add this kind of thing to #87324
DrewScoggins commentedon Aug 25, 2023
I will do that going forward, didn't know about that issue :)
DrewScoggins commentedon Aug 28, 2023
This is specifically on Windows x86.