You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If there is a way to toggle RuntimeFeature.IsDynamicCodeCompiled, should we consider turning on this flag by default on Android? I've found System.Reflection.Emit to be very impactful to startup.
Reproduction Steps
dotnet new android
Open MainActivity.cs and put _ = new Regex(@"([^;=]+)=([^;]+)(?:;\s*)?", RegexOptions.Compiled); in the OnCreate() method.
Build/run with dotnet build -c Release -t:Run
Expected behavior
RegexOptions.Compiled doesn't have a significant impact to Android startup. (Or we have a feature flag to turn it off)
Actual behavior
RegexOptions.Compiled has a significant impact to Android startup.
Regression?
From a customer's perspective they might say this is startup performance regression from Xamarin.Android to .NET 6.
Known Workarounds
Don't use RegexOptions.Compiled, use Normal.
Configuration
I tested this with the .NET 6.0.400-preview.22301.10 SDK, and the SR 1 version of .NET MAUI.
RegexOptions.Compiled is a tradeoff between startup overhead and throughput: it adds overhead to constructing the Regex in exchange for making regexes execute faster. Are you seeing this construction cost increase but throughput not also increase? If they both go up, then the opt-in flag is doing what it's supposed to do, and if a customer doesn't want to tradeoff that construction overhead for throughput, they shouldn't be setting Compiled to opt-in. (And if they want both, they should consider using the regex source generator in .NET 7 instead; this will give them both the construction perf and the throughput perf, at the expense of a bit larger IL size.)
In this case, the customer's use of RegexOptions.Compiled was coming from a NuGet owned by Microsoft. I was curious if there is some way to opt out of RuntimeFeature.IsDynamicCodeCompiled? Or some way to get back to the behavior they had from Xamarin.Android (mono/mono)?
My guess is that they thought it was still broken. I think the to-do here is to just pass on Stephen's guidance. Startup time, throughput, on disk size - pick two!
If there is a way to toggle RuntimeFeature.IsDynamicCodeCompiled, should we consider turning on this flag by default on Android? I've found System.Reflection.Emit to be very impactful to startup.
If we were to do anything, I think it would have to be through a runtime feature switch. @jonathanpeppers, how significant are we talking vs what was in mono/mono?
Don't use RegexOptions.Compile on mobile, unless you want it for throughput
Longer term it would be interesting to develop some guidance around use of the regex source generator on mobile. I guess that will emerge as folks use it.
ghost locked as resolved and limited conversation to collaborators on Jul 23, 2022
If I read right, the ctor was 174ms and IsMatch another 49ms. Now the ctor is negligible (because source gen) and IsMatch is only 6ms (because it is smarter about finding there is no possible start position, apparently)
So I think I'd expect an improvement of over 200ms, not 93.5 ms?
Also I'm curious what is causing the CompileMethod in the newer IsMatch.
My general observation, is these timings are a bit slower when traced -- especially on a mobile device running Mono. Maybe @lateralusX knows if there is some amount of overhead?
I normally test with & without tracing to double-check things.
Also I'm curious what is causing the CompileMethod in the newer IsMatch.
RuntimeHelpers.CompileMethod() is a placeholder for time spent in the runtime, so it's likely JIT'ing this method, but it could also be loading additional assemblies, etc.
My general observation, is these timings are a bit slower when traced -- especially on a mobile device running Mono. Maybe @lateralusX knows if there is some amount of overhead?
I normally test with & without tracing to double-check things.
If you run sampling it will impact process since part of sampling includes a STW (stop the world for every sample) so it will affect timers running longer than the sample interval (1ms be default). If you don't run with sampling, the impact should be minimal. It should also be mentioned that the elapsed time in speed scope is a "calculated metric", but sampling should mainly be used in relation to the total number of samples (percent, flam graphs etc) to spot methods with most samples, that is how tools like perfview visualize sample data. If you want to measure wall clock times, you should do so by add specific trace events, use custom timers or in cases there is support for low level runtime profilers use build in method enter/leave support.
Also I'm curious what is causing the CompileMethod in the newer IsMatch.
RuntimeHelpers.CompileMethod() is a placeholder for time spent in the runtime, so it's likely JIT'ing this method, but it could also be loading additional assemblies, etc.
Jupp, CompileMethod is a place holder when runtime needs to JIT compile a method. Part of JIT compile a method might results in the need to load additional types (assemblies) and potential do some other runtime work, but CompileMethod gets triggered where there is a need to JIT or load an AOT:ed method. If you run tracing with JIT/Loader events you will find what method gets JIT:ed in those events and if any additional assemblies/types needs to be loaded as part of the process.
Activity
stephentoub commentedon Jun 20, 2022
RegexOptions.Compiled is a tradeoff between startup overhead and throughput: it adds overhead to constructing the Regex in exchange for making regexes execute faster. Are you seeing this construction cost increase but throughput not also increase? If they both go up, then the opt-in flag is doing what it's supposed to do, and if a customer doesn't want to tradeoff that construction overhead for throughput, they shouldn't be setting Compiled to opt-in. (And if they want both, they should consider using the regex source generator in .NET 7 instead; this will give them both the construction perf and the throughput perf, at the expense of a bit larger IL size.)
jonathanpeppers commentedon Jun 20, 2022
In this case, the customer's use of
RegexOptions.Compiledwas coming from a NuGet owned by Microsoft. I was curious if there is some way to opt out ofRuntimeFeature.IsDynamicCodeCompiled? Or some way to get back to the behavior they had from Xamarin.Android (mono/mono)?danmoseley commentedon Jun 20, 2022
Just curious, why do they not pass it when targeting .NET Standard?
jonathanpeppers commentedon Jun 20, 2022
If you look at blame, it doesn't actually give much detail:
microsoft/appcenter-sdk-dotnet@70f7944
I'm not sure what issue they're trying to solve here either. It seems like the old code would have worked in Xamarin.Android in 2019 to me?
danmoseley commentedon Jun 20, 2022
My guess is that they thought it was still broken. I think the to-do here is to just pass on Stephen's guidance. Startup time, throughput, on disk size - pick two!
Question for @steveisok I think.
jonathanpeppers commentedon Jun 20, 2022
On Android, we would pretty much never want throughput for anything. So that is why the default setting might be interesting.
steveisok commentedon Jun 20, 2022
If we were to do anything, I think it would have to be through a runtime feature switch. @jonathanpeppers, how significant are we talking vs what was in mono/mono?
teo-tsirpanis commentedon Jun 21, 2022
@danmoseley they are targeting .NET Standard 1.0 only, which does not have
Compiled.https://github.com/microsoft/appcenter-sdk-dotnet/blob/b19ec99e16e554eb1382342b2852b16f7d8f0084/SDK/AppCenter/Microsoft.AppCenter/Microsoft.AppCenter.csproj#L6
2 remaining items
danmoseley commentedon Jun 23, 2022
Longer term it would be interesting to develop some guidance around use of the regex source generator on mobile. I guess that will emerge as folks use it.
jonathanpeppers commentedon Oct 12, 2022
It appears the new
RegexAPIs save about ~93.5ms of startup time on a Pixel 5 in a "hello world" Android app:https://github.com/jonathanpeppers/AndroidRegex
I'm working on a blog post that will mention this in detail.
stephentoub commentedon Oct 12, 2022
Nice!
danmoseley commentedon Oct 12, 2022
@jonathanpeppers I see the flame diagram there for "before", I'm curious what the flame diagram looks like "after" (if you collected it)
jonathanpeppers commentedon Oct 12, 2022
Ah yes, so
MainActivity..cctoris now so fast it is completely gone from the trace.I added the
.speedscope/.nettracefiles and more screenshots: https://github.com/jonathanpeppers/AndroidRegexdanmoseley commentedon Oct 12, 2022
If I read right, the ctor was 174ms and IsMatch another 49ms. Now the ctor is negligible (because source gen) and IsMatch is only 6ms (because it is smarter about finding there is no possible start position, apparently)
So I think I'd expect an improvement of over 200ms, not 93.5 ms?
Also I'm curious what is causing the CompileMethod in the newer IsMatch.
jonathanpeppers commentedon Oct 12, 2022
My general observation, is these timings are a bit slower when traced -- especially on a mobile device running Mono. Maybe @lateralusX knows if there is some amount of overhead?
I normally test with & without tracing to double-check things.
jonathanpeppers commentedon Oct 12, 2022
RuntimeHelpers.CompileMethod()is a placeholder for time spent in the runtime, so it's likely JIT'ing this method, but it could also be loading additional assemblies, etc.lateralusX commentedon Oct 13, 2022
If you run sampling it will impact process since part of sampling includes a STW (stop the world for every sample) so it will affect timers running longer than the sample interval (1ms be default). If you don't run with sampling, the impact should be minimal. It should also be mentioned that the elapsed time in speed scope is a "calculated metric", but sampling should mainly be used in relation to the total number of samples (percent, flam graphs etc) to spot methods with most samples, that is how tools like perfview visualize sample data. If you want to measure wall clock times, you should do so by add specific trace events, use custom timers or in cases there is support for low level runtime profilers use build in method enter/leave support.
lateralusX commentedon Oct 13, 2022
Jupp, CompileMethod is a place holder when runtime needs to JIT compile a method. Part of JIT compile a method might results in the need to load additional types (assemblies) and potential do some other runtime work, but CompileMethod gets triggered where there is a need to JIT or load an AOT:ed method. If you run tracing with JIT/Loader events you will find what method gets JIT:ed in those events and if any additional assemblies/types needs to be loaded as part of the process.