Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); More precise writebarrier for regions by MichalStrehovsky · Pull Request #98485 · dotnet/runtime · GitHub
Skip to content

More precise writebarrier for regions - #98485

Closed
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb
Closed

More precise writebarrier for regions#98485
MichalStrehovsky wants to merge 1 commit into
dotnet:mainfrom
MichalStrehovsky:regionswb

Conversation

@MichalStrehovsky

Copy link
Copy Markdown
Member

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Port of dotnet#67389 to Native AOT.
Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.
Was hoping to see similar gains as dotnet#67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:
Before:
* Total CPU Time: 33,478 msec
* Total GC CPU Time: 585 msec
* Total Allocs : 776.721 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.753 MSec/MB
* Total GC Pause: 207.7 msec
* % Time paused for Garbage Collection: 2.4%
* % CPU Time spent Garbage Collecting: 1.7%
After:
* Total CPU Time: 33,348 msec
* Total GC CPU Time: 179 msec
* Total Allocs : 771.313 MB
* Number of Heaps: 16
* GC CPU MSec/MB Alloc : 0.232 MSec/MB
* Total GC Pause: 195.8 msec
* % Time paused for Garbage Collection: 2.3%
* % CPU Time spent Garbage Collecting: 0.5%
Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Port of #67389 to Native AOT.

Adds additional checks to write barriers so that the GC can do less work. Write barriers get slower with the expectation that we'll recoup the time during garbage collections.

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC, but wallclock time got maybe 1% worse instead. However GC stats in Perfview look much better:

Before:

  • Total CPU Time: 33,478 msec
  • Total GC CPU Time: 585 msec
  • Total Allocs : 776.721 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.753 MSec/MB
  • Total GC Pause: 207.7 msec
  • % Time paused for Garbage Collection: 2.4%
  • % CPU Time spent Garbage Collecting: 1.7%

After:

  • Total CPU Time: 33,348 msec
  • Total GC CPU Time: 179 msec
  • Total Allocs : 771.313 MB
  • Number of Heaps: 16
  • GC CPU MSec/MB Alloc : 0.232 MSec/MB
  • Total GC Pause: 195.8 msec
  • % Time paused for Garbage Collection: 2.3%
  • % CPU Time spent Garbage Collecting: 0.5%

Opening as a draft because maybe we can do something to make these not as expensive (CoreCLR seems to have lots of tricks up its sleeve). We also need the Linux version.

Cc @dotnet/ilc-contrib

Author:MichalStrehovsky
Assignees:-
Labels:

area-NativeAOT-coreclr

Milestone:-

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-nativeaot-outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@cshung

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

@kunalspathak, who is working on investigating ARM64 Write Barrier performance.

@Maoni0

Copy link
Copy Markdown
Member

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@kunalspathak

Copy link
Copy Markdown
Contributor

Cc @dotnet/gc - I noticed this is not implemented on ARM64 for JIT. Is that intentional?

it was intentional as in we did not allocate time to do this work for arm64, not that this work wouldn't benefit arm64.

@Maoni0 , I will work with you offline on enabling it for arm64

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

I got some measurements for the Stage2 app with e568f75 reverted to work around #98021.

Before

Run 1

application
Max CPU Usage (%)88
Max Cores usage (%)2,461
Max Working Set (MB)140
Max Private Memory (MB)159
Build Time (ms)26,734
Start Time (ms)130
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)146
Max GC Heap Size (MB)82
Size of committed memory by the GC (MB)109
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec11.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)4,561,912
Max Gen 1 Size (B)10,358,272
Max Gen 2 Size (B)22,999,160
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,506,408,440
Max GC Heap Fragmentation (%)2,647%
# of Assemblies Loaded124
Max Exceptions (#/s)444
Max Lock Contention (#/s)1,921
Max ThreadPool Threads Count154
Max ThreadPool Queue Length166
Max ThreadPool Items (#/s)782,408
Max Active Timers137
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)140
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)736
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)79
Requests/sec233,925
Requests3,532,294
Mean latency (ms)1.11
Max latency (ms)23.03
Bad responses0
Socket errors0
Read throughput (MB/s)111.99
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.82

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,438
Max Working Set (MB)146
Max Private Memory (MB)170
Build Time (ms)26,651
Start Time (ms)133
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)153
Max GC Heap Size (MB)80
Size of committed memory by the GC (MB)117
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)2,037,384
Max Gen 1 Size (B)5,638,440
Max Gen 2 Size (B)27,481,280
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,518,408,496
Max GC Heap Fragmentation (%)4,254%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,904
Max ThreadPool Threads Count135
Max ThreadPool Queue Length129
Max ThreadPool Items (#/s)789,902
Max Active Timers155
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)146
Load CPU Usage - P90 (%)85
load
Max CPU Usage (%)26
Max Cores usage (%)737
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec234,916
Requests3,547,244
Mean latency (ms)1.10
Max latency (ms)24.28
Bad responses0
Socket errors0
Read throughput (MB/s)112.46
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.36
Latency 99th (ms)2.84

Run 3

application
Max CPU Usage (%)87
Max Cores usage (%)2,430
Max Working Set (MB)153
Max Private Memory (MB)172
Build Time (ms)26,652
Start Time (ms)144
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)160
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)122
Max Number of Gen 0 GCs / sec25.00
Max Number of Gen 1 GCs / sec8.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)22,722,240
Max Gen 1 Size (B)13,050,752
Max Gen 2 Size (B)25,841,752
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,503,312,264
Max GC Heap Fragmentation (%)6,893%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)2,069
Max ThreadPool Threads Count156
Max ThreadPool Queue Length154
Max ThreadPool Items (#/s)781,918
Max Active Timers138
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)153
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)87
Requests/sec234,420
Requests3,539,676
Mean latency (ms)1.11
Max latency (ms)21.48
Bad responses0
Socket errors0
Read throughput (MB/s)112.23
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.37
Latency 99th (ms)2.83

After

Run 1

application
Max CPU Usage (%)87
Max Cores usage (%)2,423
Max Working Set (MB)151
Max Private Memory (MB)169
Build Time (ms)26,826
Start Time (ms)137
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)88
Max Working Set (MB)158
Max GC Heap Size (MB)95
Size of committed memory by the GC (MB)120
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec10.00
Max Number of Gen 2 GCs / sec3.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)15.00
Max Gen 0 Size (B)2,843,664
Max Gen 1 Size (B)12,106,776
Max Gen 2 Size (B)25,266,304
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,527,291,080
Max GC Heap Fragmentation (%)2,750%
# of Assemblies Loaded124
Max Exceptions (#/s)456
Max Lock Contention (#/s)1,793
Max ThreadPool Threads Count141
Max ThreadPool Queue Length139
Max ThreadPool Items (#/s)794,675
Max Active Timers139
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)151
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)81
Requests/sec235,938
Requests3,562,489
Mean latency (ms)1.09
Max latency (ms)23.80
Bad responses0
Socket errors0
Read throughput (MB/s)112.95
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.60

Run 2

application
Max CPU Usage (%)87
Max Cores usage (%)2,441
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)27,528
Start Time (ms)140
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)83
Size of committed memory by the GC (MB)108
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec16.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)13.00
Max Gen 0 Size (B)2,704,488
Max Gen 1 Size (B)5,858,632
Max Gen 2 Size (B)20,285,960
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,528,119,392
Max GC Heap Fragmentation (%)2,114%
# of Assemblies Loaded124
Max Exceptions (#/s)466
Max Lock Contention (#/s)1,823
Max ThreadPool Threads Count148
Max ThreadPool Queue Length128
Max ThreadPool Items (#/s)795,102
Max Active Timers159
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)733
Max Working Set (MB)45
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)80
Requests/sec236,883
Requests3,576,893
Mean latency (ms)1.09
Max latency (ms)26.11
Bad responses0
Socket errors0
Read throughput (MB/s)113.41
Latency 50th (ms)1.06
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

Run 3

application
Max CPU Usage (%)86
Max Cores usage (%)2,416
Max Working Set (MB)139
Max Private Memory (MB)157
Build Time (ms)31,503
Start Time (ms)138
Published Size (KB)102,129
Symbols Size (KB)83,729
.NET Core SDK Version9.0.100-preview.2.24122.2
ASP.NET Core Version9.0.0-preview.2.24121.1+cc5bc6b56dc0
.NET Runtime Version9.0.0-preview.2.24122.1+d908f0030558
Max CPU Usage (%)87
Max Working Set (MB)145
Max GC Heap Size (MB)88
Size of committed memory by the GC (MB)107
Max Number of Gen 0 GCs / sec26.00
Max Number of Gen 1 GCs / sec13.00
Max Number of Gen 2 GCs / sec2.00
Max Gen 0 GC Budget (MB)70
Max Time in GC (%)14.00
Max Gen 0 Size (B)29,662,216
Max Gen 1 Size (B)6,286,400
Max Gen 2 Size (B)23,141,320
Max LOH Size (B)219,088
Max POH Size (B)1,253,192
Max Allocation Rate (B/sec)1,515,032,792
Max GC Heap Fragmentation (%)7,295%
# of Assemblies Loaded124
Max Exceptions (#/s)458
Max Lock Contention (#/s)1,733
Max ThreadPool Threads Count148
Max ThreadPool Queue Length177
Max ThreadPool Items (#/s)787,381
Max Active Timers148
IL Jitted (B)0
Methods Jitted0
Load Working Set - P90 (MB)139
Load CPU Usage - P90 (%)86
load
Max CPU Usage (%)26
Max Cores usage (%)735
Max Working Set (MB)46
Max Private Memory (MB)358
Start Time (ms)0
First Request (ms)85
Requests/sec234,890
Requests3,546,791
Mean latency (ms)1.10
Max latency (ms)26.37
Bad responses0
Socket errors0
Read throughput (MB/s)112.45
Latency 50th (ms)1.07
Latency 75th (ms)1.21
Latency 90th (ms)1.35
Latency 99th (ms)2.59

I don't see this meaninfully improving anything.

It's possible this is only viable if we can get the write barrier to be cheaper like CoreCLR does it (by basically regenerating write barriers as needed).

I almost feel like we shouldn't proceed with this. It doesn't seem worth the risk I'm creating with all of this extra assembly. I'd hate to introduce a bug in a write barrier.

@jkotas

jkotas commented Feb 23, 2024

Copy link
Copy Markdown
Member

Was hoping to see similar gains as #67389 (comment) for our self-hosted ILC

Are you able to replicate this result on current main? You can configure the write barrier to use using DOTNET_GCWriteBarrier. It is possible that both ILC and GC have been optimized in the meantime and the more precise write barrier does not make a difference for it anymore.

The more precise write barrier should help the most for workloads that have large heap, a lot of churn in Gen2 -> Gen0 references, and run on a machine with a lot of cores. For the two benchmarks you have tried:

  • The heap in Goldilocks microbenchmarks is probably too small
  • The heap in ILC may be big enough, but there is not enough churn in Gen2 -> Gen0 references for the precise write barrier to make a difference.

The workloads with a lot of churn in Gen2 -> Gen0 references are often workloads that were optimized to use pools extensively. Object pools violate the generational hypothesis and make the GC to do more work. The precise write barrier is compensating for it somewhat.

@VSadov

VSadov commented Feb 24, 2024

Copy link
Copy Markdown
Member

I don't see this meaninfully improving anything.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time. According to the literature (i.e. The GC book) improving barrier precision is not always a win as overall math may work against you.

For a simple example:
Let's say you spend 6% time in the barrier and 3% time in the collector.
It is easy to regress the barrier by 10%, since it is such a small piece of code. But then you need to make collector 20% faster just to break even. Which could be hard.
You`d often need considerable wins in collector to justify extra barrier complexity. It is not impossible (thus barriers are typically not completely "dumb"), but hard.

Another reason for moving work into the barrier could be to shorten pauses. That is if it reduces work that must run during pauses, like compaction. However, if it just reduces cost of marking, which can run in the background, then impact on pauses is less interesting.

When I was making NativeAOT barriers up to date with CoreCLR, I was not sure if this extra precision is necessarily a win, so I was not too eager with porting it.
(the part that we did not do this on all the platforms in CoreCLR, was another hint that missing this piece is not a huge deal)

@MichalStrehovsky

Copy link
Copy Markdown
MemberAuthor

Are you able to replicate this result on current main?

I guess we'll see if this is still meaningful if/when it gets enabled for ARM64 and it gets re-measured on CoreCLR-JIT.

Note that the change effectively moves a portion of "GC tax" from the collector, which runs rarely, to the barrier which runs all the time.

Yep, that's what I meant with "Write barriers get slower with the expectation that we'll recoup the time during garbage collections." in the top post. The CoreCLR version of this PR had some really good numbers associated with it (#67389 (comment)) but it doesn't match what I'm seeing.

CoreCLR is able to get rid of some indirections in the write barriers due to run-time patching of the assembly code, but I'm not sure if that explains that instead of seeing a 6% improvement, we see nothing/maybe even a small regression.

I would probably be able to come up with a microbenchmark where this helps a lot (also one where it hurts a lot) but we don't have any real-word benchmark we're using with native AOT where this helps.

I'm going to close this. It doesn't look like it's worth spending time porting this to the Linux version and then living with the fear that a GC hole got introduced due to me messing up the assembly and nobody noticing it in review.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Mar 27, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@MichalStrehovsky@cshung@Maoni0@kunalspathak@jkotas@VSadov