') + ')', '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('^' + ".*" + ', '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" + ', '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('^' + ".*" + ', '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); } })(); })(); JIT: Support delegate GDV guards in loop cloning by jakobbotsch · Pull Request #75140 · dotnet/runtime · GitHub
Skip to content

JIT: Support delegate GDV guards in loop cloning - #75140

Merged
jakobbotsch merged 12 commits into
dotnet:mainfrom
jakobbotsch:delegate-gdv-loop-cloning
Oct 20, 2022
Merged

JIT: Support delegate GDV guards in loop cloning#75140
jakobbotsch merged 12 commits into
dotnet:mainfrom
jakobbotsch:delegate-gdv-loop-cloning

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Sep 6, 2022

Copy link
Copy Markdown
Member

Add support for cloning loops based on delegate GDV guards. Mark delegate address loads as invariant to allow VN and CSE of them.

For:

[MethodImpl(MethodImplOptions.NoInlining)]privatestaticlongFoo(Func<long,long>f){longresult=0;for(longi=0;i<100000;i++)result+=f(i);returnresult;}

Before (GDV guard for the delegate target on every iteration):

; Assembly listing for method Program:Foo(System.Func`2[long,long]):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; Tier-1 compilation; optimized code; optimized using profile data; rsp based frame; fully interruptible; with Dynamic PGO: edge weights are valid, and fgCalledCount is 3; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data; Final local variable assignments;; V00 arg0 [V00,T03] ( 5, 66783 ) ref -> rsi class-hnd single-def; V01 loc0 [V01,T01] ( 4,133563.67) long -> rdi; V02 loc1 [V02,T00] ( 6,267125 ) long -> rbx; V03 OutArgs [V03 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace"; V04 tmp1 [V04,T02] ( 3,133562 ) long -> rbp "guarded devirt return temp";* V05 tmp2 [V05 ] ( 0, 0 ) ref -> zero-ref class-hnd "guarded devirt this exact temp";; Lcl frame size = 40G_M6926_IG01: ;; offset=0000H57pushrdi56pushrsi55pushrbp53pushrbx 4883EC28 subrsp,40 488BF1 movrsi,rcx ;; size=11 bbWeight=1 PerfScore 4.50G_M6926_IG02: ;; offset=000BH 33FF xoredi,edi 33DB xorebx,ebx ;; size=4 bbWeight=1 PerfScore 0.50G_M6926_IG03: ;; offset=000FH 48B8084A3BB2FD7F0000 movrax,0x7FFDB23B4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax 751F jne SHORT G_M6926_IG07 486BEB2A imulrbp,rbx,42 ;; size=20 bbWeight=66781 PerfScore 417381.25G_M6926_IG04: ;; offset=0023H 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CDD jl SHORT G_M6926_IG03 ;; size=15 bbWeight=66781 PerfScore 116866.75G_M6926_IG05: ;; offset=0032H 488BC7 movrax,rdi ;; size=3 bbWeight=0.67 PerfScore 0.17G_M6926_IG06: ;; offset=0035H 4883C428 addrsp,40 5B poprbx 5D poprbp 5E poprsi 5F poprdi C3 ret ;; size=9 bbWeight=0.67 PerfScore 2.17G_M6926_IG07: ;; offset=003EH 488B4E08 movrcx, gword ptr [rsi+08H] 488BD3 movrdx,rbx FF5618 call[rsi+18H]System.Func`2[long,long]:Invoke(long):long:this 488BE8 movrbp,rax EBD6 jmp SHORT G_M6926_IG04 ;; size=15 bbWeight=0 PerfScore 0.00; Total bytes of code 77, prolog size 11, PerfScore 534263.03, instruction count 28, allocated bytes for code 77 (MethodHash=f7dde4f1) for method Program:Foo(System.Func`2[long,long]):long; ============================================================

After (loop cloned with no GDV guard checks inside the loop):

; Assembly listing for method Program:Foo(System.Func`2[long,long]):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; Tier-1 compilation; optimized code; optimized using profile data; rsp based frame; fully interruptible; with Dynamic PGO: edge weights are invalid, and fgCalledCount is 3; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data; Final local variable assignments;; V00 arg0 [V00,T03] ( 7, 671.83) ref -> rsi class-hnd single-def; V01 loc0 [V01,T01] ( 6,133567.00) long -> rdi; V02 loc1 [V02,T00] ( 10,267131.67) long -> rbx; V03 OutArgs [V03 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace"; V04 tmp1 [V04,T02] ( 5,133565.33) long -> rbp "guarded devirt return temp";* V05 tmp2 [V05 ] ( 0, 0 ) ref -> zero-ref class-hnd "guarded devirt this exact temp";; Lcl frame size = 40G_M6926_IG01: ;; offset=0000H57pushrdi56pushrsi55pushrbp53pushrbx 4883EC28 subrsp,40 488BF1 movrsi,rcx ;; size=11 bbWeight=1 PerfScore 4.50G_M6926_IG02: ;; offset=000BH 33FF xoredi,edi 33DB xorebx,ebx 4885F6 testrsi,rsi7425je SHORT G_M6926_IG05 48B8084A3CB2FD7F0000 movrax,0x7FFDB23C4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax7515jne SHORT G_M6926_IG05 ;; size=25 bbWeight=1 PerfScore 6.00G_M6926_IG03: ;; offset=0024H 486BEB2A imulrbp,rbx,42 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CED jl SHORT G_M6926_IG03 ;; size=19 bbWeight=66114.84 PerfScore 247930.65G_M6926_IG04: ;; offset=0037H EB23 jmp SHORT G_M6926_IG07 ;; size=2 bbWeight=1 PerfScore 2.00G_M6926_IG05: ;; offset=0039H 48B8084A3CB2FD7F0000 movrax,0x7FFDB23C4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax 751F jne SHORT G_M6926_IG09 486BEB2A imulrbp,rbx,42 ;; size=20 bbWeight=667.83 PerfScore 4173.92G_M6926_IG06: ;; offset=004DH 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CDD jl SHORT G_M6926_IG05 ;; size=15 bbWeight=667.83 PerfScore 1168.70G_M6926_IG07: ;; offset=005CH 488BC7 movrax,rdi ;; size=3 bbWeight=0.67 PerfScore 0.17G_M6926_IG08: ;; offset=005FH 4883C428 addrsp,40 5B poprbx 5D poprbp 5E poprsi 5F poprdi C3 ret ;; size=9 bbWeight=0.67 PerfScore 2.17G_M6926_IG09: ;; offset=0068H 488B4E08 movrcx, gword ptr [rsi+08H] 488BD3 movrdx,rbx FF5618 call[rsi+18H]System.Func`2[long,long]:Invoke(long):long:this 488BE8 movrbp,rax EBD6 jmp SHORT G_M6926_IG06 ;; size=15 bbWeight=0 PerfScore 0.00; Total bytes of code 119, prolog size 11, PerfScore 253300.00, instruction count 39, allocated bytes for code 119 (MethodHash=f7dde4f1) for method Program:Foo(System.Func`2[long,long]):long; ============================================================

Note that we are not eliminating the GDV check in the cold loop. In cases where we the only cloning condition we have is the GDV guard we should do that. But I'm not sure if I should fold that into this PR too.

Micro benchmark:

[Benchmark]publiclongBench(){longsum=0;for(inti=0;i<30;i++)sum+=SumVals(j =>j*42);returnsum;}[MethodImpl(MethodImplOptions.NoInlining)]privatestaticlongSumVals(Func<long,long>f){longresult=0;for(longi=0;i<10000000;i++)result+=f(i);returnresult;}
MethodJobEnvironmentVariablesToolchainMeanErrorStdDevRatio
BenchJob-LKWCTIEmptymain443.48 ms0.382 ms0.319 ms3.50
BenchJob-MGSBRDDOTNET_TieredPGO=1main126.67 ms0.331 ms0.309 ms1.00
BenchJob-RKOAHTDOTNET_TieredPGO=1pr62.92 ms0.287 ms0.255 ms0.50

cc @dotnet/jit-contrib @davidfowl

* Support cloning loops based on delegate GDV guards
* Do flow-graph opts directly in loop cloning instead of relying on RBO
to clean it up (for both type and delegate GDV)
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 6, 2022
@ghostghost assigned jakobbotschSep 6, 2022
@ghost

ghost commented Sep 6, 2022

Copy link
Copy Markdown

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

Issue Details
  • Support cloning loops based on delegate GDV guards
  • Do flow-graph opts directly in loop cloning instead of relying on RBO
    to clean it up (for both type and delegate GDV)

For:

[MethodImpl(MethodImplOptions.NoInlining)]privatestaticlongFoo(Func<long,long>f){longresult=0;for(longi=0;i<100000;i++)result+=f(i);returnresult;}

Before:

; Assembly listing for method Program:Foo(System.Func`2[long,long]):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; Tier-1 compilation; optimized code; optimized using profile data; rsp based frame; fully interruptible; with Dynamic PGO: edge weights are valid, and fgCalledCount is 3; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data; Final local variable assignments;; V00 arg0 [V00,T03] ( 5, 66783 ) ref -> rsi class-hnd single-def; V01 loc0 [V01,T01] ( 4,133563.67) long -> rdi; V02 loc1 [V02,T00] ( 6,267125 ) long -> rbx; V03 OutArgs [V03 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace"; V04 tmp1 [V04,T02] ( 3,133562 ) long -> rbp "guarded devirt return temp";* V05 tmp2 [V05 ] ( 0, 0 ) ref -> zero-ref class-hnd "guarded devirt this exact temp";; Lcl frame size = 40G_M6926_IG01: ;; offset=0000H57pushrdi56pushrsi55pushrbp53pushrbx 4883EC28 subrsp,40 488BF1 movrsi,rcx ;; size=11 bbWeight=1 PerfScore 4.50G_M6926_IG02: ;; offset=000BH 33FF xoredi,edi 33DB xorebx,ebx ;; size=4 bbWeight=1 PerfScore 0.50G_M6926_IG03: ;; offset=000FH 48B8084A3BB2FD7F0000 movrax,0x7FFDB23B4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax 751F jne SHORT G_M6926_IG07 486BEB2A imulrbp,rbx,42 ;; size=20 bbWeight=66781 PerfScore 417381.25G_M6926_IG04: ;; offset=0023H 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CDD jl SHORT G_M6926_IG03 ;; size=15 bbWeight=66781 PerfScore 116866.75G_M6926_IG05: ;; offset=0032H 488BC7 movrax,rdi ;; size=3 bbWeight=0.67 PerfScore 0.17G_M6926_IG06: ;; offset=0035H 4883C428 addrsp,40 5B poprbx 5D poprbp 5E poprsi 5F poprdi C3 ret ;; size=9 bbWeight=0.67 PerfScore 2.17G_M6926_IG07: ;; offset=003EH 488B4E08 movrcx, gword ptr [rsi+08H] 488BD3 movrdx,rbx FF5618 call[rsi+18H]System.Func`2[long,long]:Invoke(long):long:this 488BE8 movrbp,rax EBD6 jmp SHORT G_M6926_IG04 ;; size=15 bbWeight=0 PerfScore 0.00; Total bytes of code 77, prolog size 11, PerfScore 534263.03, instruction count 28, allocated bytes for code 77 (MethodHash=f7dde4f1) for method Program:Foo(System.Func`2[long,long]):long; ============================================================

After:

; Assembly listing for method Program:Foo(System.Func`2[long,long]):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; Tier-1 compilation; optimized code; optimized using profile data; rsp based frame; fully interruptible; with Dynamic PGO: edge weights are invalid, and fgCalledCount is 3; 0 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data; Final local variable assignments;; V00 arg0 [V00,T03] ( 7, 671.83) ref -> rsi class-hnd single-def; V01 loc0 [V01,T01] ( 6,133567.00) long -> rdi; V02 loc1 [V02,T00] ( 10,267131.67) long -> rbx; V03 OutArgs [V03 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace"; V04 tmp1 [V04,T02] ( 5,133565.33) long -> rbp "guarded devirt return temp";* V05 tmp2 [V05 ] ( 0, 0 ) ref -> zero-ref class-hnd "guarded devirt this exact temp";; Lcl frame size = 40G_M6926_IG01: ;; offset=0000H57pushrdi56pushrsi55pushrbp53pushrbx 4883EC28 subrsp,40 488BF1 movrsi,rcx ;; size=11 bbWeight=1 PerfScore 4.50G_M6926_IG02: ;; offset=000BH 33FF xoredi,edi 33DB xorebx,ebx 4885F6 testrsi,rsi7425je SHORT G_M6926_IG05 48B8084A3CB2FD7F0000 movrax,0x7FFDB23C4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax7515jne SHORT G_M6926_IG05 ;; size=25 bbWeight=1 PerfScore 6.00G_M6926_IG03: ;; offset=0024H 486BEB2A imulrbp,rbx,42 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CED jl SHORT G_M6926_IG03 ;; size=19 bbWeight=66114.84 PerfScore 247930.65G_M6926_IG04: ;; offset=0037H EB23 jmp SHORT G_M6926_IG07 ;; size=2 bbWeight=1 PerfScore 2.00G_M6926_IG05: ;; offset=0039H 48B8084A3CB2FD7F0000 movrax,0x7FFDB23C4A08 ; code for <>c:<Main>b__0_048394618cmp qword ptr [rsi+18H],rax 751F jne SHORT G_M6926_IG09 486BEB2A imulrbp,rbx,42 ;; size=20 bbWeight=667.83 PerfScore 4173.92G_M6926_IG06: ;; offset=004DH 4803FD addrdi,rbp 48FFC3 incrbx 4881FBA0860100 cmprbx,0x186A0 7CDD jl SHORT G_M6926_IG05 ;; size=15 bbWeight=667.83 PerfScore 1168.70G_M6926_IG07: ;; offset=005CH 488BC7 movrax,rdi ;; size=3 bbWeight=0.67 PerfScore 0.17G_M6926_IG08: ;; offset=005FH 4883C428 addrsp,40 5B poprbx 5D poprbp 5E poprsi 5F poprdi C3 ret ;; size=9 bbWeight=0.67 PerfScore 2.17G_M6926_IG09: ;; offset=0068H 488B4E08 movrcx, gword ptr [rsi+08H] 488BD3 movrdx,rbx FF5618 call[rsi+18H]System.Func`2[long,long]:Invoke(long):long:this 488BE8 movrbp,rax EBD6 jmp SHORT G_M6926_IG06 ;; size=15 bbWeight=0 PerfScore 0.00; Total bytes of code 119, prolog size 11, PerfScore 253300.00, instruction count 39, allocated bytes for code 119 (MethodHash=f7dde4f1) for method Program:Foo(System.Func`2[long,long]):long; ============================================================

Note that we are not eliminating the GDV check in the cold loop. In cases where we the only cloning condition we have is the GDV guard we should do that. But I'm not sure if I should fold that into this PR too.

Micro benchmark:

[Benchmark]publiclongBench(){longsum=0;for(inti=0;i<30;i++)sum+=SumVals(j =>j*42);returnsum;}[MethodImpl(MethodImplOptions.NoInlining)]privatestaticlongSumVals(Func<long,long>f){longresult=0;for(longi=0;i<10000000;i++)result+=f(i);returnresult;}
MethodJobEnvironmentVariablesToolchainMeanErrorStdDevRatio
BenchJob-LKWCTIEmptymain443.48 ms0.382 ms0.319 ms3.50
BenchJob-MGSBRDDOTNET_TieredPGO=1main126.67 ms0.331 ms0.309 ms1.00
BenchJob-RKOAHTDOTNET_TieredPGO=1pr62.92 ms0.287 ms0.255 ms0.50

cc @dotnet/jit-contrib @davidfowl

Author:jakobbotsch
Assignees:-
Labels:

area-CodeGen-coreclr

Milestone:-

@jakobbotsch
jakobbotschforce-pushed the delegate-gdv-loop-cloning branch from 5cefd6c to a5afa33CompareSeptember 6, 2022 16:51
// cold case for the previous GDV. Then we can reuse the target
// if the second check of a chained GDV fails.
bool reuseTarget = (origCall->gtCallMoreFlags & GTF_CALL_M_GUARDED_DEVIRT_CHAIN) == 0;
bool reuseTarget = false; //(origCall->gtCallMoreFlags & GTF_CALL_M_GUARDED_DEVIRT_CHAIN) == 0;

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.

Should this be disabled just for method GDV?

@jakobbotschjakobbotschSep 8, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Do you mean for vtable GDV only, and not for delegate GDV? This code does not run for class-based GDV.

As you can probably guess it's necessary because otherwise we cannot recognize the test in loop cloning. However, it does introduce an extra load on the cold path. Actually this seems fine on xarch as we can contain the load in the hot compare, so in a simple case of:

privatestaticFunc<long,long>s_f;[MethodImpl(MethodImplOptions.NoInlining)]privatestaticlongFoo(){longresult=0;for(longi=0;i<100000;i++)result+=s_f(i);returnresult;}

The diff looks like:

@@ -33,19 +32,18 @@ G_M54051_IG02:
mov rbx, 0xD1FFAB1E ; data for Program:s_f
;; size=14 bbWeight=1 PerfScore 0.75
G_M54051_IG03:
- mov rcx, gword ptr [rbx]- mov r8, qword ptr [rcx+18H]+ mov r8, gword ptr [rbx]
mov rax, 0xD1FFAB1E ; code for <>c:<Main>b__0_0
- cmp r8, rax+ cmp qword ptr [r8+18H], rax
jne SHORT G_M54051_IG07
imul rbp, rdi, 42
G_M54051_IG04:
add rsi, rbp
inc rdi
cmp rdi, 0x186A0
jl SHORT G_M54051_IG03
G_M54051_IG05:
mov rax, rsi
;; size=3 bbWeight=0.67 PerfScore 0.17
@@ -58,12 +56,12 @@ G_M54051_IG06:
ret
;; size=9 bbWeight=0.67 PerfScore 2.17
G_M54051_IG07:
- mov rcx, gword ptr [rcx+08H]+ mov rcx, gword ptr [r8+08H]
mov rdx, rdi
- call r8+ call [r8+18H]System.Func`2[long,long]:Invoke(long):long:this
mov rbp, rax
jmp SHORT G_M54051_IG04
- ;; size=15 bbWeight=0 PerfScore 0.00+ ;; size=16 bbWeight=0 PerfScore 0.00

On arm it's worse (depending on register pressure) since we have to load the delegate address into a register anyway, so we just stop reusing this register on the cold path:

@@ -43,19 +42,19 @@ G_M54051_IG02:
movk x23, #0xD1FFAB1E LSL #32
;; size=40 bbWeight=1 PerfScore 5.00
G_M54051_IG03:
- ldr x1, [x22]- ldr x2, [x1, #0x18]- cmp x2, x23+ ldr x2, [x22]+ ldr x0, [x2, #0x18]+ cmp x0, x23
bne G_M54051_IG07
mov x0, #42
mul x24, x20, x0
G_M54051_IG04:
add x19, x19, x24
add x20, x20, #1
cmp x20, x21
blt G_M54051_IG03
G_M54051_IG05:
mov x0, x19
;; size=4 bbWeight=0.67 PerfScore 0.33
@@ -67,12 +66,13 @@ G_M54051_IG06:
ret lr
;; size=20 bbWeight=0.67 PerfScore 3.33
G_M54051_IG07:
- ldr x0, [x1, #0x08]+ ldr x0, [x2, #0x08]
mov x1, x20
+ ldr x2, [x2, #0x18]
blr x2
mov x24, x0
b G_M54051_IG04
- ;; size=20 bbWeight=0 PerfScore 0.00+ ;; size=24 bbWeight=0 PerfScore 0.00

My opinion here is that this should ultimately be left up to CSE to decide. We may want to look at whether there is some CSE heuristics we should change for ARM given that less containment is possible there, so creating a CSE def in a hot block for a CSE use in a cold block may be more attractive (edit: opened #75253 for this).

This also does put delegate GDV in line with virtual class GDV. We do not reuse the method table indir in the cold case for virtual class GDVs either, even though we could.

@AndyAyersMSAndyAyersMS 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.

This looks good.

@jakobbotschjakobbotsch added this to the 8.0.0 milestone Sep 15, 2022
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr pgo, runtime-coreclr libraries-pgo

@azure-pipelines

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

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

Lots of libraries-pgo test failures, but I am not sure if they are related. Looks like we have a bunch of the same failures in main that need to be sorted through.

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr libraries-pgo

@azure-pipelines

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

@jakobbotsch

jakobbotsch commented Oct 18, 2022

Copy link
Copy Markdown
MemberAuthor

libraries-pgo failures look like #77144, #77143 and #75827. NAOT build failure is #76801 according to build analysis.

@jakobbotsch
jakobbotsch marked this pull request as ready for review October 18, 2022 09:30
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @AndyAyersMS

@AndyAyersMS

Copy link
Copy Markdown
Member

Are any of the SPMI diffs showing new cloning? I would have expected we have enough data there to inspire at least a few cases.

@AndyAyersMSAndyAyersMS 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.

Changes LGTM.

@BruceForstall you might want to review as well.

@jakobbotsch

jakobbotsch commented Oct 19, 2022

Copy link
Copy Markdown
MemberAuthor

Are any of the SPMI diffs showing new cloning? I would have expected we have enough data there to inspire at least a few cases.

This is the one example I found:

foreach(TSourceelementinsource)
{
d.Add(keySelector(element),elementSelector(element));
}

Loop cloning triggers for both a type test and the two delegate calls, but RBO is not able to clean up any of the cases:

DominatorBB24 of BB30 has relop with same liberal VNN006 ( 8, 15) [000595] J--X---N---NE int $14fN004 ( 4, 4) [000593] #--X-------├──▌IND long $202N003 ( 2, 2) [000592] -------N---└──▌ADD byref $341N001 ( 1, 1) [000590] -----------├──▌LCL_VAR ref V03 arg2 u:1 $c2
N002 ( 1, 1) [000591] -----------└──▌CNS_INT long 24 $300N005 ( 3, 10) [000594] H----------└──▌CNS_INT(h) long 0x7fff74602f00 ftn $2c2
Redundant compare; current relop:
N006 ( 8, 15) [000197] J--X---N---NE int $14fN004 ( 4, 4) [000195] #--X-------├──▌IND long $202N003 ( 2, 2) [000194] -------N---└──▌ADD byref $341N001 ( 1, 1) [000192] -----------├──▌LCL_VAR ref V03 arg2 u:1 $c2
N002 ( 1, 1) [000193] -----------└──▌CNS_INT long 24 $300N005 ( 3, 10) [000196] H----------└──▌CNS_INT(h) long 0x7fff74602f00 ftn $2c2
Fall through successor BB25 of BB24 reaches, relop [000197] must be falseCurrent relop has exception side effect and is in a try, so we won't optimize
...
DominatorBB23 of BB27 has relop with same liberal VNN006 ( 8, 15) [000588] J--X---N---NE int $14dN004 ( 4, 4) [000586] #--X-------├──▌IND long $201N003 ( 2, 2) [000585] -------N---└──▌ADD byref $340N001 ( 1, 1) [000583] -----------├──▌LCL_VAR ref V02 arg1 u:1 $c1
N002 ( 1, 1) [000584] -----------└──▌CNS_INT long 24 $300N005 ( 3, 10) [000587] H----------└──▌CNS_INT(h) long 0x7fff74602ee8 ftn $2c1
Redundant compare; current relop:
N006 ( 8, 15) [000171] J--X---N---NE int $14dN004 ( 4, 4) [000169] #--X-------├──▌IND long $201N003 ( 2, 2) [000168] -------N---└──▌ADD byref $340N001 ( 1, 1) [000166] -----------├──▌LCL_VAR ref V02 arg1 u:1 $c1
N002 ( 1, 1) [000167] -----------└──▌CNS_INT long 24 $300N005 ( 3, 10) [000170] H----------└──▌CNS_INT(h) long 0x7fff74602ee8 ftn $2c1
Fall through successor BB24 of BB23 reaches, relop [000171] must be falseCurrent relop has exception side effect and is in a try, so we won't optimize
...
DominatorBB25 of BB34 has relop with same liberal VNN004 ( 7, 13) [000600] J--X---N---NE int $151N002 ( 3, 2) [000598] #--X-------├──▌IND long $203N001 ( 1, 1) [000597] -----------└──▌LCL_VAR ref V10 loc5 u:1 $c6
N003 ( 3, 10) [000599] H----------└──▌CNS_INT(h) long 0x7fff7466ce98class$2c3Redundant compare; current relop:
N004 ( 7, 13) [000224] J--X---N---NE int $151N002 ( 3, 2) [000222] #--X-------├──▌IND long $203N001 ( 1, 1) [000221] -----------└──▌LCL_VAR ref V10 loc5 u:1 $c6
N003 ( 3, 10) [000223] H----------└──▌CNS_INT(h) long 0x7fff7466ce98class$2c3Fall through successor BB26 of BB25 reaches, relop [000224] must be falseCurrent relop has exception side effect and is in a try, so we won't optimize

I'm not sure if the intention is to rely on early prop to remove these flags, but I guess the simplest way is to just have loop cloning remove these flags on its own.

This prevents RBO from optimizing them away if the loop is inside an EH
handler (e.g. foreach loop).
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

@AndyAyersMS Can you please look at the latest commit, which makes the indirs nonfaulting in the "hot" loop for both type tests and delegate address tests?

Also, I checked via an assert and there are 13 instances that trigger loop cloning with delegate address guards in the aspnet collection.

@jakobbotsch

jakobbotsch commented Oct 19, 2022

Copy link
Copy Markdown
MemberAuthor

An interesting case we get in the asp.net collection are the two inner loops in
System.Collections.Generic.ArraySortHelper`1[int]:PickPivotAndPartition(System.Span`1[int],System.Comparison`1[int]):int:

while(comparer(keys[++left],pivot)<0);
while(comparer(pivot,keys[--right])<0);

We hit missing information after the cloning, so I cannot see the exact diff. I'll try to measure how it affects performance of sorting.

@AndyAyersMS

Copy link
Copy Markdown
Member

I'm not sure if the intention is to rely on early prop to remove these flags, but I guess the simplest way is to just have loop cloning remove these flags on its own

Fixing it here is good, but do we know why early prop can't do this too?

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

Fixing it here is good, but do we know why early prop can't do this too?

Early prop only looks for GT_NULLCHECK nodes, I think. It does not know about the dominating compare that cloning has added.

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

If we get #71707 in and start running early prop on all blocks we might be able to expand it to look for the relops too.

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

Effect on sorting seems pretty much within variance:

privatestaticreadonlyint[]s_nums=Enumerable.Range(0,1000000).ToArray();[Benchmark]publicintRandom(){Randomrand=newRandom(5);int[]nums=s_nums;for(inti=0;i<nums.Length;i++)nums[i]=rand.Next(50);Array.Sort(nums,(a,b)=>b-a);returnnums.Length;}[Benchmark]publicintAscending(){int[]nums=s_nums;for(inti=0;i<nums.Length;i++)nums[i]=i;Array.Sort(nums,(a,b)=>b-a);returnnums.Length;}[Benchmark]publicintDescending(){int[]nums=s_nums;for(inti=0;i<nums.Length;i++)nums[i]=nums.Length-i;Array.Sort(nums,(a,b)=>b-a);returnnums.Length;}[Benchmark]publicintEqual(){int[]nums=s_nums;for(inti=0;i<nums.Length;i++)nums[i]=0;Array.Sort(nums,(a,b)=>b-a);returnnums.Length;}
MethodJobEnvironmentVariablesToolchainMeanErrorStdDevRatio
RandomJob-VJODMVEmptymain45.048 ms0.2455 ms0.2297 ms1.00
RandomJob-GFIHSHDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0main-FullPGO30.161 ms0.2018 ms0.1789 ms0.67
RandomJob-ZZPKHXDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0pr-FullPGO30.692 ms0.1856 ms0.1736 ms0.68
AscendingJob-VJODMVEmptymain47.859 ms0.0860 ms0.0763 ms1.00
AscendingJob-GFIHSHDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0main-FullPGO16.344 ms0.0323 ms0.0302 ms0.34
AscendingJob-ZZPKHXDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0pr-FullPGO16.357 ms0.0441 ms0.0391 ms0.34
DescendingJob-VJODMVEmptymain24.597 ms0.0492 ms0.0460 ms1.00
DescendingJob-GFIHSHDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0main-FullPGO8.838 ms0.0414 ms0.0346 ms0.36
DescendingJob-ZZPKHXDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0pr-FullPGO8.714 ms0.0272 ms0.0254 ms0.35
EqualJob-VJODMVEmptymain24.191 ms0.0317 ms0.0265 ms1.00
EqualJob-GFIHSHDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0main-FullPGO11.398 ms0.0191 ms0.0179 ms0.47
EqualJob-ZZPKHXDOTNET_TieredPGO=1,DOTNET_ReadyToRun=0pr-FullPGO11.581 ms0.0259 ms0.0230 ms0.48

I do notice that we end up moving some of the hot basic blocks inside the loop to the end of the function, so might be we could do better with some smarter flow graph linearization. And of course, if we cloned the outer loop instead of the two inner loops it would probably be better too.

@jakobbotsch
jakobbotsch merged commit 17d613e into dotnet:mainOct 20, 2022
@jakobbotsch
jakobbotsch deleted the delegate-gdv-loop-cloning branch October 20, 2022 09:20
@ghostghost locked as resolved and limited conversation to collaborators Nov 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jakobbotsch@AndyAyersMS