Uh oh!
There was an error while loading. Please reload this page.
[NativeAOT] Follow up changes for the suspension loop routine. - #73741
Conversation
VSadov
commented
Aug 11, 2022
I think this can be reviewed/merged. We can tune this a bit further, but we may get more improvements from fixing issues like in #73655, which is v.Next |
I've instrumented the code while developing to report suspension timings and looked at what reported by library tests. The latency results are noisy, but there was nothing extraordinary before or after the changes. |
VSadov
commented
Aug 11, 2022
I've also looked at timings reported by the following benchmark. usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Threading;classProgram{staticobject[]arr1=newException[10000];enumE1{a}staticobjectbox=E1.a;staticvoidDoSomeWork(){vara1=arr1;ArgumentNullExceptionval=newArgumentNullException();for(;;){Assign(a1,1,val);Assign(a1,2,val);Assign(a1,3,val);Assign(a1,4,val);Assign(a1,5,val);Assign(a1,6,val);Assign(a1,7,val);}}staticbooldoPoll;[MethodImpl(MethodImplOptions.NoInlining)]privatestaticvoidAssign(object[]a,inti,ArgumentNullExceptionval){if(val!=null){a[i]=val;}if(doPoll){// enum->int unbox will perform a GC-polling FCALL (easy case for suspension)// int unboxed = (int)box;// unboxing is not an FCall in NativeAOT and does not poll. Hard to find benign operation that polls.// use PInvoke instead as an "easy for suspension" caseThread.Yield();}a[i]=null;}staticvoidMain(){for(inti=0;i<Environment.ProcessorCount-1;i++){newThread(()=>{DoSomeWork();}).Start();}Thread.Sleep(10);for(;;){longlongest=0;intiterations=0;doPoll^=true;// do 5sec of suspension/resumevarsw=Stopwatch.StartNew();while(sw.ElapsedMilliseconds<5000){longoneIter=sw.ElapsedMilliseconds;// Suspend/Resume EEGC.GetTotalAllocatedBytes(precise:true);oneIter=sw.ElapsedMilliseconds-oneIter;longest=Math.Max(longest,oneIter);iterations++;// Let other threads actually resumeThread.Yield();}sw.Stop();{Console.WriteLine($"frequent GC-polling : {doPoll}");Console.WriteLine($"#suspensions in 5 sec. : {iterations}");Console.WriteLine($"Average suspension took : {(double)sw.ElapsedMilliseconds/iterations}");Console.WriteLine($"longest suspend was (msec): {longest}");Console.WriteLine();}}}} |
VSadov
commented
Aug 11, 2022
Typical results on Windows: |
VSadov
commented
Aug 11, 2022
Typical results on Unix (WSL2 Ubuntu 18.04 on the same machine) |
|
jkotas
commented
Aug 11, 2022
Do you think we can refactor the thread suspension such that majority of the code is shared between NativeAOT and CoreCLR? It would be the ultimate place to be. |
VSadov
commented
Aug 11, 2022
The main differences are call outs to hijack routines, detection if a thread is “suspended” and interaction with debugger and maybe threadabort support. |
VSadov
commented
Aug 12, 2022
/azp run runtime-extra-platforms |
|
Azure Pipelines successfully started running 1 pipeline(s). |
MichalStrehovsky
commented
Aug 12, 2022
/azp run runtime-extra-platforms |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Now I see a bunch of cryptography and COM Interop failures. |
VSadov
commented
Aug 12, 2022
Linux and Linux musl both fail with: |
VSadov
commented
Aug 12, 2022
Windows (both x64 and arm64): or |
MichalStrehovsky
commented
Aug 12, 2022
Exists in the baseline - ignore. This API is trimming unsafe and it looks like it ran out of luck. We'll annotate it like that.
These are new tests from #72051 that just merged. We'll disable them. The feature doesn't work with trimming or AOT. |
VSadov
commented
Aug 12, 2022
I've created a PR with a trivial noop change #73856. |
VSadov
commented
Aug 12, 2022
Thanks!!! |
Last item for #67805
Turns out polling, while simpler, works better than CoreCLR-style event handshake, so I am keeping the pure polling style, while making it a bit more robust - added an observe-only pass, set a limit for per-iteration wait, avoid hijacking already redirected threads, etc. Otherwise it is the same algorithm as before.
In CoreCLR implementation, user threads signal an event as they suspend and suspending thread waits on the event with a 1 msec timeout. Porting that here resulted in regressions.
The problem is when we do timeout, and that happens often enough as some threads may not be hijacked on a first try, - that results in 1 msec delay, which is considerable, and on some OSes 15 msec, which is really a lot.
It is probably amplified by the fact that in NativeAOT synchronous GC polls are relatively rare, unless a thread does a lot of allocations or pinvokes.
We should reconsider using the event pattern in CoreCLR as well. It is questionable if the complexity adds any benefits.