Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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" + '
Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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('^' + ".*" + ' Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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('^' + ".*" + ' Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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" + ' Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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('^' + ".*" + ' Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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('^' + ".*" + ' Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9
, '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); } })(); })(); Arm64: Implement region write barriers by a74nh · Pull Request #111636 · dotnet/runtime · GitHub
Skip to content

Arm64: Implement region write barriers - #111636

Merged
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github
May 17, 2025
Merged

Arm64: Implement region write barriers#111636
Maoni0 merged 52 commits into
dotnet:mainfrom
a74nh:precisewritebarriers_github

Conversation

@a74nh

@a74nha74nh commented Jan 20, 2025

Copy link
Copy Markdown
Contributor

(@Maoni0 will merge this PR when all the data is collected)

Extend the Arm64 writebarrier function to support regions and use the WriteBarrierManager, similar to Amd64. This results in 10 different versions of the JIT_WriteBarrier, with the WriteBarrierManager deciding on which version to use.

Pseudo code for the writebarrier is included in GC-write-barriers.md

This is expected to make the writebarrier slower, but improve the performance of the GC. DOTNET_GCWriteBarrier=3 can be used give the same functionality as before this change.

The behavior of the writebarrier is:
Before the PR: check ephemeral bounds, update a byte in the card table, mark the card bundle
After the PR:
DOTNET_GCWriteBarrier=1 (default, bit region write barriers): check ephemeral bounds, check regions, update a bit in the card table, mark the card bundle
DOTNET_GCWriteBarrier=2 (byte region write barriers): check ephemeral bounds, check regions, update a byte in the card table, mark the card bundle
DOTNET_GCWriteBarrier=3 (server write barriers): check ephemeral bounds, update a byte in the card table, mark the card bundle. This is the same as before the PR.
DOTNET_gcServer=1: update a byte in the card table, mark the card bundle.

Test results on an 8 core Cobalt 100.

Ephemeral test (dotnet/performance)

WB_nonephemeral : -20%
WB_ephemeral: -16%

WKS GC is calculating the generation of regions in addition to comparing with g_ephemeral_low/high". So while it might set fewer cards, it is more expensive and it shows.

With DOTNET_GCWriteBarrier=3:
WB_nonephemeral : +15%
WB_ephemeral: +1%

SVR GC WB also became more expensive but it sets way fewer cards (for nonephemeral it should set almost no cards).

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set:
Gen0 pause: -21.06%. Gen1 pause -14.25%

DOTNET_GCWriteBarrier=2:
Gen0 pause: -6.7%. Gen1 pause -2.78%

DOTNET_GCWriteBarrier=3 :
Gen0 pause: -1.37%. Gen1 pause -1.26%

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
Gen0 pause: -7.24%. Gen1 pause -3.49%

Above are linux numbers. On windows for no env var set we are seeing not as much but still quite noticeable pause improvements around 8% to 10% for this config of GCPerfSim.

Baseline13608Diff: 13608Diff %: 13608
Process ID1973213608
Process Namecoreruncorerun
Commandlinecorerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0corerun.exe GCPerfSim.dll -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0
Process Duration (Sec)35.94532.834-3.111-8.655
Total Allocated MB215,230.37215,263.8733.5050.016
Max Size Peak MB4,444.054,505.4061.3571.381
GC Count38,865.0038,728.00-137-0.353
Heap Count1100
Gen0 Count3,076.003,646.0057018.531
Gen1 Count35,774.0035,067.00-707-1.976
Ephemeral Count38,850.0038,713.00-137-0.353
Gen2 Blocking Count1100
BGC Count141400
Gen0 Total Pause Time MSec1,302.021,386.4184.3886.481
Gen1 Total Pause Time MSec16,992.4214,964.89-2,027.52-11.932
Ephemeral Total Pause Time MSec18,294.4316,351.30-1,943.14-10.621
Blocking Gen2 Total Pause Time MSec2.3192.271-0.048-2.07
BGC Total Pause Time MSec4.2254.440.2155.081
GC Pause Time %50.91449.82-1.093-2.148
Avg. Gen0 Pause Time (ms)0.4230.38-0.043-10.165
Avg. Gen1 Pause Time (ms)0.4750.427-0.048-10.156
Avg. Gen0 Promoted (mb)0.8620.8-0.061-7.119
Avg. Gen1 Promoted (mb)0.7830.7870.0040.573
Avg. Gen0 Speed (mb/ms)2.0362.1050.0693.391
Avg. Gen1 Speed (mb/ms)1.6481.8450.19711.943

Looking at the card marking speed it's clearly improved -

image

Orchard CMS benchmark

+~2% reqs/sec

Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
Comment threadsrc/coreclr/vm/arm64/patchedcode.S Outdated
@kunalspathak

kunalspathak commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

FYI - @Maoni0
@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I also have a bunch of notes where I rewrote the AMD64 and ARM64 write barrier assembly in pseudo code. I'll tidy up and add somewhere in docs/

Comment threadsrc/coreclr/vm/arm64/asmhelpers.S Outdated
@EgorBo

Copy link
Copy Markdown
Member

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh I'm just curious, is this ready for benchmarks? (on linux-arm64)

I think all the failures are fixed up now. So, yes, this would be a good time. If you've got something to run that'd be great.

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@EgorBo

Copy link
Copy Markdown
Member

I've been using your orchard.sh script that runs on a single machine, on 4 cores (+1 for wrk). I don't see any improvement in reqs per sec, although not sure if that's a good enough test.

Afair it's not bottle-necked in Write-Barrier + presumably, your PR is supposed to decrease average GC pause rather than WB's throughput? So you might want to look at the GC stats? the orchard.sh should have USE_DOTNET_TRACE property that you need to set to 1 to grab traces (and set DOTNET_TRACE_ARGS to listen to gc events specifically)

@EgorBo

Copy link
Copy Markdown
Member

@EgorBot -linux_azure_cobalt100 -linux_azure_ampere -profiler

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@EgorBo

EgorBo commented Jan 23, 2025

Copy link
Copy Markdown
Member

I guess it's sort of expected that it's slower throughput wise in microbenchmarks. the WB_nonephemeral perf is mostly here: https://gist.github.com/EgorBot/a6db6579aba05de6a25f111513cb54b2#file-diff_asm_bcd38073-asm-L30 which is, I guess,

 // Check whether the region we're storing into is gen 0- nothing to do in this case ldrb w12,[x12] cbz w12, LOCAL_LABEL(Exit)

(I guess I should've added an extra benchmark where object we're storing is gen2)

PS: feel free to call the bot yourself if needed

Comment threadsrc/coreclr/vm/gcenv.ee.cpp Outdated
@mrsharm

Copy link
Copy Markdown
Member

FYI - @Maoni0@mrsharm@cshung - what preliminary tests can we run to validate the performance impact?

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

  1. Not removing the outliers: --outliers DontRemove.
  2. Setting a fixed number of invocations that'll be high enough to reduce the standard error: --invocationCount {InvocationCount}
  3. Setting a fixed number of iterations: --iterationCount 20.
- System.Numerics.Tests.Perf_BigInteger.Add(arguments: 65536*)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 1000, *)
- System.Tests.Perf_GC<Byte>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Char>.AllocateUninitializedArray(length: 10000, *)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Byte>.NewOperator_Array(length: 10000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 1000)
- System.Tests.Perf_GC<Char>.NewOperator_Array(length: 10000)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Numerics.Tests.Perf_BigInteger.Subtract(arguments: 65536*)
- System.Collections.CtorGivenSize<String>.Array(size: 512)
- ByteMark.BenchBitOps
- System.IO.Tests.Perf_File.ReadAllBytes(size: 104857600)
- System.IO.Tests.Perf_File.ReadAllBytesAsync(size: 104857600)
- System.Linq.Tests.Perf_Enumerable.ToArray*
- System.Collections.Tests.Perf_BitArray.BitArrayByteArrayCtor(size: 512)

Once the microbenchmarks are run, the pertinent metrics would be the % difference in the time of execution of a test + the standard error of tests.

As a note: the following for the regression that was created because of us moving to a More Precise Write Barrier for x64: #73783 - seems like one of the affected microbenchmarks is already in the aforementioned list. I remember StackWalk being extremely volatile but still worth trying out with.

@cshung

Copy link
Copy Markdown
Contributor

As we run the benchmarks, I would pay attention to ephemeral GC pause time, in particular the time spent on marking cards.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

Sorry for the delay. I would run the microbenchmarks with and without this change on the pertinent hardware on the following tests given below for a sufficient number of iterations (as some of these exhibit a considerable amount of variance). The other considerations while running these is to ensure that the number of GCs is equivalent between the baseline and the comparand - this can be achieved by:

running most of the tests as suggested, I don't see any differences. Everything seems within error margins:


| Method | Job | Toolchain | length | pinned | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------------- |----------- |----------------------------------------------------------------------------------- |------- |------- |------------:|----------:|----------:|------------:|------------:|-----------:|------:|---------------- |--------:|-------:|-------:|-------:|----------:|------------:|
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 129.78 ns | 53.253 ns | 61.326 ns | 118.07 ns | 108.50 ns | 388.8 ns | 1.08 | Baseline | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 137.49 ns | 53.415 ns | 61.512 ns | 125.97 ns | 116.80 ns | 396.9 ns | 1.15 | Same | 0.54 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.60 ns | 51.462 ns | 59.263 ns | 89.10 ns | 88.63 ns | 354.8 ns | 1.11 | Baseline | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | False | 103.35 ns | 51.294 ns | 59.070 ns | 88.76 ns | 88.21 ns | 353.4 ns | 1.10 | Same | 0.65 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 744.34 ns | 7.498 ns | 8.634 ns | 741.62 ns | 735.19 ns | 764.7 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 743.07 ns | 9.170 ns | 10.561 ns | 740.52 ns | 732.56 ns | 763.7 ns | 1.00 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 735.06 ns | 10.791 ns | 12.426 ns | 728.98 ns | 720.78 ns | 757.2 ns | 1.00 | Baseline | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | True | 748.82 ns | 8.844 ns | 10.185 ns | 743.99 ns | 736.23 ns | 767.8 ns | 1.02 | Same | 0.02 | 0.6364 | 0.6364 | 0.6364 | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 626.94 ns | 39.042 ns | 44.961 ns | 618.03 ns | 588.73 ns | 805.0 ns | 1.00 | Baseline | 0.09 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 623.92 ns | 74.318 ns | 85.585 ns | 601.31 ns | 589.99 ns | 983.1 ns | 1.00 | Same | 0.15 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 142.84 ns | 17.866 ns | 20.575 ns | 138.18 ns | 134.39 ns | 228.9 ns | 1.01 | Baseline | 0.17 | 0.2879 | - | - | 19.55 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | False | 149.25 ns | 16.513 ns | 19.016 ns | 146.35 ns | 137.79 ns | 227.3 ns | 1.06 | Same | 0.16 | 0.2879 | - | - | 19.55 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,592.21 ns | 32.371 ns | 37.278 ns | 2,585.44 ns | 2,550.16 ns | 2,707.3 ns | 1.00 | Baseline | 0.02 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,475.21 ns | 76.425 ns | 88.011 ns | 2,436.47 ns | 2,379.59 ns | 2,637.6 ns | 0.96 | Same | 0.04 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| AllocateUninitializedArray | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,438.40 ns | 43.482 ns | 50.074 ns | 2,444.35 ns | 2,330.27 ns | 2,527.3 ns | 1.00 | Baseline | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| AllocateUninitializedArray | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | True | 2,449.01 ns | 35.429 ns | 40.800 ns | 2,448.20 ns | 2,338.34 ns | 2,520.9 ns | 1.00 | Same | 0.03 | 6.3182 | 6.3182 | 6.3182 | 19.56 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 98.53 ns | 49.747 ns | 57.289 ns | 86.26 ns | 74.80 ns | 340.4 ns | 1.11 | Baseline | 0.67 | 0.0152 | - | - | 1.98 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1000 | ? | 95.01 ns | 48.560 ns | 55.922 ns | 80.60 ns | 79.98 ns | 331.4 ns | 1.07 | Same | 0.66 | 0.0152 | - | - | 1.98 KB | 1.00 |
| | | | | | | | | | | | | | | | | | | |
| NewOperator_Array | Job-CWSSJX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 546.14 ns | 49.634 ns | 57.159 ns | 533.12 ns | 520.12 ns | 784.7 ns | 1.01 | Baseline | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| NewOperator_Array | Job-EDJSHX | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 10000 | ? | 551.71 ns | 52.751 ns | 60.748 ns | 537.58 ns | 528.97 ns | 807.3 ns | 1.02 | Same | 0.13 | 0.2879 | - | - | 19.55 KB | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 205.72 ns | 129.897 ns | 149.589 ns | 84.26 ns | 71.82 ns | 404.32 ns | 1.78 | Baseline | 1.85 | - | 160 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 203.72 ns | 129.080 ns | 148.649 ns | 83.54 ns | 72.15 ns | 400.73 ns | 1.76 | Same | 1.84 | - | 160 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 25.58 ns | 0.439 ns | 0.505 ns | 25.63 ns | 23.68 ns | 26.00 ns | 1.00 | Baseline | 0.03 | - | - | NA |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 24.67 ns | 1.307 ns | 1.506 ns | 24.99 ns | 21.90 ns | 26.31 ns | 0.97 | Same | 0.06 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Add | Job-VIYVLB | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,591.60 ns | 74.221 ns | 85.473 ns | 3,559.69 ns | 3,555.19 ns | 3,919.99 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8224 B | 1.00 |
| Add | Job-VRIONI | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,571.79 ns | 69.881 ns | 80.475 ns | 3,551.91 ns | 3,546.31 ns | 3,911.55 ns | 0.99 | Same | 0.03 | 0.1212 | 8224 B | 1.00 |
| Method | Job | Toolchain | arguments | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------- |----------- |----------------------------------------------------------------------------------- |----------------- |------------:|-----------:|-----------:|------------:|------------:|------------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 145.80 ns | 116.856 ns | 134.571 ns | 72.70 ns | 72.08 ns | 426.39 ns | 1.59 | Baseline | 1.70 | - | 152 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 1024,1024 bits | 143.24 ns | 118.524 ns | 136.493 ns | 72.22 ns | 71.90 ns | 431.54 ns | 1.57 | Same | 1.72 | - | 152 B | 1.00 |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.41 ns | 0.836 ns | 0.963 ns | 26.88 ns | 24.34 ns | 27.34 ns | 1.00 | Baseline | 0.05 | - | - | NA |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 16,16 bits | 26.22 ns | 0.666 ns | 0.767 ns | 26.29 ns | 24.35 ns | 27.18 ns | 0.99 | Same | 0.05 | - | - | NA |
| | | | | | | | | | | | | | | | |
| Subtract | Job-KDZVCP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,483.97 ns | 61.051 ns | 70.306 ns | 3,466.17 ns | 3,458.38 ns | 3,780.31 ns | 1.00 | Baseline | 0.03 | 0.1212 | 8216 B | 1.00 |
| Subtract | Job-KKPRIL | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 65536,65536 bits | 3,526.84 ns | 71.010 ns | 81.775 ns | 3,504.11 ns | 3,480.61 ns | 3,840.66 ns | 1.01 | Same | 0.03 | 0.1212 | 8216 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------- |----------------------------------------------------------------------------------- |----- |---------:|--------:|--------:|---------:|---------:|---------:|------:|---------------- |--------:|-------:|----------:|------------:|
| Array | Job-CZKOLC | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 152.8 ns | 7.44 ns | 8.56 ns | 149.6 ns | 147.4 ns | 186.8 ns | 1.00 | Baseline | 0.07 | 0.0606 | 4.02 KB | 1.00 |
| Array | Job-FQHBTF | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 155.3 ns | 4.66 ns | 5.36 ns | 154.5 ns | 151.6 ns | 177.2 ns | 1.02 | Same | 0.06 | 0.0606 | 4.02 KB | 1.00 |
| Method | Job | Toolchain | input | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|-------- |----------- |----------------------------------------------------------------------------------- |------------ |----------:|---------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 41.88 ns | 9.097 ns | 10.476 ns | 37.78 ns | 36.30 ns | 80.16 ns | 1.04 | Baseline | 0.30 | 0.0061 | 424 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | ICollection | 43.15 ns | 9.478 ns | 10.915 ns | 36.91 ns | 36.21 ns | 79.58 ns | 1.07 | Same | 0.31 | 0.0061 | 424 B | 1.00 |
| | | | | | | | | | | | | | | | |
| ToArray | Job-QHOIJP | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 287.98 ns | 5.110 ns | 5.885 ns | 286.38 ns | 285.61 ns | 312.59 ns | 1.00 | Baseline | 0.03 | 0.0061 | 456 B | 1.00 |
| ToArray | Job-GOWGBS | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | IEnumerable | 289.73 ns | 4.845 ns | 5.580 ns | 287.99 ns | 287.74 ns | 313.07 ns | 1.01 | Same | 0.03 | 0.0061 | 456 B | 1.00 |
| Method | Job | Toolchain | Size | Mean | Error | StdDev | Median | Min | Max | Ratio | MannWhitney(2%) | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------------- |----------- |----------------------------------------------------------------------------------- |----- |----------:|----------:|----------:|----------:|----------:|----------:|------:|---------------- |--------:|-------:|----------:|------------:|
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 21.91 ns | 11.631 ns | 13.395 ns | 14.75 ns | 14.65 ns | 57.33 ns | 1.24 | Baseline | 0.88 | - | 64 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 4 | 22.39 ns | 11.757 ns | 13.540 ns | 15.89 ns | 15.70 ns | 60.19 ns | 1.27 | Same | 0.90 | - | 64 B | 1.00 |
| | | | | | | | | | | | | | | | |
| BitArrayByteArrayCtor | Job-WNOFTX | /runtime_base/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 142.08 ns | 5.946 ns | 6.848 ns | 140.73 ns | 138.30 ns | 170.18 ns | 1.00 | Baseline | 0.06 | 0.0076 | 568 B | 1.00 |
| BitArrayByteArrayCtor | Job-QPXJRV | /runtime_table/artifacts/tests/coreclr/linux.arm64.Release/Tests/Core_Root/corerun | 512 | 139.35 ns | 5.774 ns | 6.650 ns | 137.68 ns | 136.98 ns | 167.37 ns | 0.98 | Same | 0.06 | 0.0076 | 568 B | 1.00 |

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@EgorBot -linux_ampere -linux_cobalt100 -windows_cobalt100 -profiler --envvars DOTNET_GCWriteBarrier:3

usingBenchmarkDotNet.Attributes;publicclassMyBench{objectDst1;objectDst2;objectDst3;objectDst4;staticobjectValue=new();staticMyBench(){GC.Collect();GC.Collect();}[Benchmark]publicvoidWB_nonephemeral(){// Write non-ephemeral referenceDst1=Value;Dst2=Value;Dst3=Value;Dst4=Value;}[Benchmark]publicvoidWB_ephemeral(){// Write non-ephemeral referenceDst1=newobject();}}

@a74nh

a74nh commented Mar 20, 2025

Copy link
Copy Markdown
ContributorAuthor

WriteBarrier 3 results are a little better than expected. With this we're using the old writebarrier, except it has one or two fewer checks to do. With this it's showing a gain. Oddly windows has 20% gain for nonephemeral!

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I noticed that the ShadowUpdate code is never called, as g_GCShadow is always 0. It is only ever set if DOTNET_HeapVerify is set.

Removing the g_GCShadow checks from the writebarrier gives:

DOTNET_GCWriteBarrier=0

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-HYVKPPHEAD4.407 ns0.0572 ns1.00-
WB_nonephemeralJob-HJOGLMPR4.511 ns0.0579 ns1.02-
WB_ephemeralJob-HYVKPPHEAD12.036 ns0.2587 ns1.000.0003
WB_ephemeralJob-HJOGLMPR12.548 ns0.2553 ns1.040.0003

DOTNET_GCWriteBarrier=3

MethodJobToolchainMeanErrorRatioGen0
WB_nonephemeralJob-BEPCOQHEAD4.421 ns0.1165 ns1.00-
WB_nonephemeralJob-VWEJCQPR3.195 ns0.0033 ns0.72-
WB_ephemeralJob-BEPCOQHEAD11.898 ns0.0826 ns1.000.0003
WB_ephemeralJob-VWEJCQPR11.674 ns0.1842 ns0.980.0003

It has removed all the slowdown added by this PR, and given additional perf when writebarrier=3.

Looking at Am64, when g_GCShadow is set, it uses JIT_WriteBarrier_Debug in jithelpers_slow.S. Annoyingly it's another complete copy of the writebarrier function. I'll look at doing something similar for Arm64 - either by doing it the same way or extending writebarriermanager to switch on shadow too, giving us 16 functions. Either way I want to write the assembly using the macros to avoid copy/paste errors.

(Note I'll be away for 2 weeks, so will implement when I get back)

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I did some runs of Orchard CMS based on Egor's script, on Cobalt 100:

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

So a couple of percent better overall with the PR.

I tried with the GCShadow checks removed, but figures looks identical to the PR.

@jkotas

Copy link
Copy Markdown
Member

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

I tried with the GCShadow checks removed, but figures looks identical to the PR.

GCShadow should be present in debug and checked builds of the runtime only. They should not be present in release builds of the runtime.

I assume that all perf measurements are done on a release build. Is that correct? So it makes sense that removing GCShadow checks has no impact on the results.

Yes, on a release build WRITE_BARRIER_CHECK shouldn't be defined. I'll double check to make sure I've been using release for the micro benchmarks.

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Orchard CMS results were using a Release build. So the figures above, with ~100 Requests/sec improvement are correct.

However, my Ephemeral tests were using a Checked build. Here's using a Release build. These match better to the EgorBot results.

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

@a74nh

a74nh commented Apr 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Is there any additional testing anyone wanted?

@Maoni0

Copy link
Copy Markdown
Member

I'm back from vacation and have asked @a74nh to please edit the original description of this PR to include a summary of the perf results so we'll have an easier time to know the perf behavior (instead of having to read many comments on the PR).

@a74nh

a74nh commented Apr 17, 2025

Copy link
Copy Markdown
ContributorAuthor

Test Results

This comment will be extended as I gather results. This contains more details for the perf results in the top message. I intend to keep this comment up to date with the latest results

All run on an 8 core Cobalt 100, Ubuntu 24.04.2

Ephemeral test (dotnet/performance)

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0046 ns1.00-
WB_nonephemeralPR4.489 ns0.0077 ns1.20-
WB_ephemeralHEAD5.314 ns0.1318 ns1.000.0004
WB_ephemeralPR6.176 ns0.0531 ns1.160.0003

With DOTNET_GCWriteBarrier=3

MethodToolchainMeanErrorRatioGen0
WB_nonephemeralHEAD3.742 ns0.0077 ns1.00-
WB_nonephemeralPR3.168 ns0.0063 ns0.85-
WB_ephemeralHEAD5.467 ns0.0790 ns1.000.0004
WB_ephemeralPR5.409 ns0.0538 ns0.990.0003

GCPerfsim

Flags: -tc 2 -tagb 200 -tlgb 2 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -21.06%
AverageGen1PauseTimeDiffPercentage -14.25%
AverageGen0Count: 2624 -> 2744
AverageGen1Count: 680 -> 673

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -6.7%
AverageGen1PauseTimeDiffPercentage -2.78%
AverageGen0Count: 3048 -> 3044
AverageGen1Count: 659 -> 659

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage -1.37%
AverageGen1PauseTimeDiffPercentage -1.26%
AverageGen0Count: 3047 -> 3048
AverageGen1Count: 660 -> 658

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.24%
AverageGen1PauseTimeDiffPercentage -3.49%
AverageGen0Count: 239 -> 239
AverageGen1Count: 81 -> 81

Flags: -tc 2 -tagb 200 -tlgb 8 -lohpi 0 -sohsi 50 -ramb 20 -rlmb 0.2 -sohpi 0

No environment variables set (bit region write barriers):
AverageGen0PauseTimeDiffPercentage -13.69%
AverageGen1PauseTimeDiffPercentage -5.7%
AverageGen0Count: 2957 -> 2957
AverageGen1Count: 750 -> 749

DOTNET_GCWriteBarrier=2 (byte region write barriers):
AverageGen0PauseTimeDiffPercentage -5.94%
AverageGen1PauseTimeDiffPercentage -1.19%
AverageGen0Count: 2958 -> 2959
AverageGen1Count: 749 -> 749

DOTNET_GCWriteBarrier=3 (server write barriers):
AverageGen0PauseTimeDiffPercentage +0.07%
AverageGen1PauseTimeDiffPercentage 0.00%
AverageGen0Count: 2960 -> 2957
AverageGen1Count: 748 -> 750

DOTNET_gcServer=1 DOTNET_GCHeapCount=8:
AverageGen0PauseTimeDiffPercentage -7.4%
AverageGen1PauseTimeDiffPercentage -3.04%
AverageGen0Count: 233 -> 233
AverageGen1Count: 81 -> 81

Orchard CMS benchmark

HEAD:
Requests/sec: 5171.91
Requests/sec: 5201.02
Requests/sec: 5235.64

PR:
Requests/sec: 5326.45
Requests/sec: 5309.99
Requests/sec: 5298.49

Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
Comment threaddocs/design/coreclr/jit/GC-write-barriers.md Outdated
@Maoni0

Copy link
Copy Markdown
Member

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

@a74nh

Copy link
Copy Markdown
ContributorAuthor

@a74nh and I have been looking at the profiles and we need to do a new run as the runs from before was doing mostly gen1 GCs and there were very few gen0 GCs which made the comparison not meaningful. we did notice some problem with the runs @a74nh did where the BGC pause times were much higher with the fix build which I was going to take a look at.

The higher pause times were due to issues in the way the results were being gathered, which has now been fixed.

New runs of the GCperfSim have been done with a meaningful number of GC collections.

Full results here: #111636 (comment)

The best result is -21.06% Gen0 pause time and -14.25% gen1 pause time.

Meanwhile, GCWriteBarrier=3 is showing now change from head (as we wanted).

A reduced version is in the top comment.

@Maoni0Maoni0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much, @a74nh, for your contribution and being patient with the perf data collection, discussion and meetings at odd hours :) this work is greatly appreciated!

@Maoni0
Maoni0 enabled auto-merge (squash) May 16, 2025 23:20
Change-Id: Ia4f89dce9cb5aeedeeac16e54b7e35e9f255f68b
@Maoni0
Maoni0 merged commit e2ad5fc into dotnet:mainMay 17, 2025
@a74nh
a74nh deleted the precisewritebarriers_github branch May 17, 2025 08:46
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 17, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-arm64area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@a74nh@kunalspathak@EgorBo@mrsharm@cshung@jkotas@Maoni0@mangod9