Uh oh!
There was an error while loading. Please reload this page.
Enable FEATURE_MULTICASTSTUB_AS_IL for Windows x86 - #104192
Conversation
huoyaoyuan
commented
Jun 29, 2024
@EgorBot -intel -amd -arm64 usingBenchmarkDotNet.Attributes;usingSystem;namespaceBenchmarkGround{publicstructGCStruct{publicobjecta,b,c,d,e,f,h,g;}publicclassBench{privatereadonlyobjectobj=newobject();privatereadonlyGuidguid=Guid.NewGuid();privatereadonlyGCStructgcStruct=newGCStruct{a=newobject(),g=newobject()};privatereadonlyAction<int>singleArg=(Action<int>)delegate{}+delegate{};privatereadonlyAction<int>manyCast=(Action<int>)delegate{}+delegate{}+delegate{}+delegate{}+delegate{}+delegate{};privatereadonlyFunc<int,double,Guid,GCStruct,object,double>manyArg_RetFPU=(Func<int,double,Guid,GCStruct,object,double>)delegate{return123.456;}+delegate{return654.321;};[Benchmark]publicvoidSingleArg()=>singleArg(42);[Benchmark]publicvoidManyCast()=>manyCast(42);[Benchmark]publicdoubleManyArg_RetFPU()=>manyArg_RetFPU(42,123.0,guid,gcStruct,obj);}} |
Tagging subscribers to this area: @mangod9 |
EgorBot
commented
Jun 29, 2024
Benchmark results on Intel
|
EgorBot
commented
Jun 29, 2024
Benchmark results on Arm64
|
EgorBot
commented
Jun 29, 2024
Benchmark results on Amd
|
| pCode->EmitBRTRUE(invokeTraceHelper); | ||
| pCode->EmitBR(debuggerCheckEnd); // Tune branch prediction to prefer non-debugging path | ||
| pCode->EmitLabel(invokeTraceHelper); | ||
| pCode->EmitLoadThis(); | ||
| pCode->EmitLDLOC(dwLoopCounterNum); | ||
| pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0); |
There was a problem hiding this comment.
| pCode->EmitBRTRUE(invokeTraceHelper); | |
| pCode->EmitBR(debuggerCheckEnd); // Tune branch prediction to prefer non-debugging path | |
| pCode->EmitLabel(invokeTraceHelper); | |
| pCode->EmitLoadThis(); | |
| pCode->EmitLDLOC(dwLoopCounterNum); | |
| pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0); | |
| pCode->EmitBRTRUE(invokeTraceHelper); |
And move the debugging path to be after RET:
pCode->EmitRET();
#ifdef DEBUGGING_SUPPORTED
// Emit debugging support at the end of the method for better perf
pCode->EmitLabel(invokeTraceHelper);
pCode->EmitLoadThis();
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0);
pCode->EmitBR(debuggerCheckEnd);
#endif
This should be even better
There was a problem hiding this comment.
That was exactly what I did locally, and resulted in exact same codegen with current, at least for x86. The compiled asm code block order did follow IL block order exactly.
@dotnet/jit-contrib Do you have any suggestion on this?
There was a problem hiding this comment.
This code has atypical loop. It looks like the JIT tried to reorder the basic blocks to turn it a more regular loop. I am not sure whether there is anything to fix in the JIT (the JIT would have to have profile data to do better).
It may be still worth it to move TraceHelper call to be at the end in IL. It provides stronger hint about the desired code layout to the JIT and makes this optimization a bit less fragile.
There was a problem hiding this comment.
Well normalizing the loop closer to a for loop results in about 5% improvement for ManyArg case, but 5% regression for ManyCast.
There was a problem hiding this comment.
Codegen for x64 now:
; Assembly listing for method System.Action:IL_STUB_MulticastDelegate_Invoke():this (FullOpts); Emitting BLENDED_CODE for X64 with AVX - Windows; FullOpts code; optimized code; optimized using Synthesized PGO; rsp based frame; partially interruptible; with Synthesized PGO: fgCalledCount is 100; No PGO dataG_M000_IG01: ;; offset=0x0000pushrsipushrbxsubrsp,40movrbx,rcxG_M000_IG02: ;; offset=0x0009xoresi,esicmp qword ptr [rbx+0x30],0jle SHORT G_M000_IG05G_M000_IG03: ;; offset=0x0012test dword ptr [(reloc 0x7ff824c55210)],512jne SHORT G_M000_IG06G_M000_IG04: ;; offset=0x001Emovrcx, gword ptr [rbx+0x28]cmpesi, dword ptr [rcx+0x08]jae SHORT G_M000_IG07movrax, gword ptr [rcx+8*rsi+0x10]movrcx, gword ptr [rax+0x08]call[rax+0x18]System.Action:Invoke():thisincesimovsxdrcx,esicmprcx, qword ptr [rbx+0x30]jl SHORT G_M000_IG03G_M000_IG05: ;; offset=0x003Eaddrsp,40poprbxpoprsiretG_M000_IG06: ;; offset=0x0045movrcx,rbxmovedx,esicall System.StubHelpers.StubHelpers:MulticastDebuggerTraceHelper(System.Object,int)jmp SHORT G_M000_IG04G_M000_IG07: ;; offset=0x0051call CORINFO_HELP_RNGCHKFAILint3; Total bytes of code 87
jkotas
left a comment
There was a problem hiding this comment.
LGTM otherwise. Nice simplification!
Uh oh!
There was an error while loading. Please reload this page.
| pCode->EmitBRTRUE(invokeTraceHelper); | ||
| pCode->EmitBR(debuggerCheckEnd); // Tune branch prediction to prefer non-debugging path | ||
| pCode->EmitLabel(invokeTraceHelper); | ||
| pCode->EmitLoadThis(); | ||
| pCode->EmitLDLOC(dwLoopCounterNum); | ||
| pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0); |
There was a problem hiding this comment.
This code has atypical loop. It looks like the JIT tried to reorder the basic blocks to turn it a more regular loop. I am not sure whether there is anything to fix in the JIT (the JIT would have to have profile data to do better).
It may be still worth it to move TraceHelper call to be at the end in IL. It provides stronger hint about the desired code layout to the JIT and makes this optimization a bit less fragile.
Uh oh!
There was an error while loading. Please reload this page.
| pCode->EmitLDC(0); | ||
| pCode->EmitSTLOC(dwLoopCounterNum); | ||
| // Make the shape of the loop similar to what C# compiler emits |
There was a problem hiding this comment.
The new shape seems to be missing the invocation of the trace helper at the end. E.g. if the invocation count is 2, the trace helper should be called 3 times. It is only called 2 times if I am reading the code correctly.
There was a problem hiding this comment.
Fixed. Result codegen:
; Assembly listing for method System.Action:IL_STUB_MulticastDelegate_Invoke():this (FullOpts); Emitting BLENDED_CODE for X64 with AVX - Windows; FullOpts code; optimized code; optimized using Synthesized PGO; rsp based frame; partially interruptible; with Synthesized PGO: fgCalledCount is 100; No PGO dataG_M000_IG01: ;; offset=0x0000pushrsipushrbxsubrsp,40movrbx,rcxG_M000_IG02: ;; offset=0x0009xoresi,esijmp SHORT G_M000_IG04G_M000_IG03: ;; offset=0x000Dmovrcx, gword ptr [rbx+0x28]cmpesi, dword ptr [rcx+0x08]jae SHORT G_M000_IG08movrax, gword ptr [rcx+8*rsi+0x10]movrcx, gword ptr [rax+0x08]call[rax+0x18]System.Action:Invoke():thisincesiG_M000_IG04: ;; offset=0x0024test dword ptr [(reloc 0x7ff84ea05210)],512jne SHORT G_M000_IG06G_M000_IG05: ;; offset=0x0030movsxdrcx,esicmprcx, qword ptr [rbx+0x30]jl SHORT G_M000_IG03jmp SHORT G_M000_IG07G_M000_IG06: ;; offset=0x003Bmovrcx,rbxmovedx,esicall System.StubHelpers.StubHelpers:MulticastDebuggerTraceHelper(System.Object,int)jmp SHORT G_M000_IG05G_M000_IG07: ;; offset=0x0047addrsp,40poprbxpoprsiretG_M000_IG08: ;; offset=0x004Ecall CORINFO_HELP_RNGCHKFAILint3; Total bytes of code 84JIT reorders the debugging block in front of the ret. Benchmark shows no regression. It's just a bit strange with the unconditional jumps.
Closes#103958 .
Local benchmark result for
Action<int>on x86: