Repository navigation
Developers can precompile their Regex code for faster startup #44676
Description
AB#1255830
Regex has an interpreted mode and a compiled mode. The compiled mode takes longer to start, but is generally faster. Some users want both startup time and performance; other users want to run on a platform where JITted code is not allowed, and also want performance. For those users, .NET Framework allowed the generated code to be saved out but .NET Core/.NET 5 does not plan to support saving emitted IL.
Instead, we could develop a Roslyn Source Generator that would generate the necessary code (as C#) at compile time.
Essentially API should be need on Regex, as the generated code would hook into it in the regular way, by deriving from RegexRunner and implementing FindFirstChar() and Go(). This API is all public/protected today (indeed it happens that .NET Core can load regexes written out by .NET Framework.)
TBD the user experience - eg., how one must annotate regexes in order to trigger the generator - assuming it doesn't attempt to read all the code to try to infer them - and how the generated code is wired up.
For example, one could imagine requiring an annotation like this (we could also expose API on the source generator so it could consume a list of regexes, for example)
[RegexSourceGenerator(“(?:[0-9]{1,3}\.){3}[0-9]{1,3}”, RegexOptions.IgnoreCase)]
public partial Regex CreateIpAddressRegex();
This new attribute would need to be exposed somewhere, probably on the Regex assembly since the code being compiled would be referencing that anyway.
Also TBD the implementation details necessary to wire into the existing Regex implementation - this is probably most of the work.
cc @pgovind @eerhardt @stephentoub this is just a summary of our email thread. Anything to add?
In terms of customers, we have at least one major 1st party service that heavily uses regex and highly values both throughput and startup time. This is probably P1 for them.
@danroth27 in a Blazor app, can I assume that most users in the .NET 6 timeframe will be content with either using Javascript regexes, or interpreted mode .NET regexes? In my mind this is P2/P3.
@marek-safar similar question for Xamarin, on platforms that don't JIT, Xamarin apps presumably currently interpret the ref-emitted code, or use the regex interpreted mode - and this would not be a big win for them - we had talked about P2.
Activity
danmoseley commentedon Nov 14, 2020
cc @mjsabby
[-]Create a source generator for Regex[/-][+]Source generator for Regex for faster startup[/+]am11 commentedon Nov 14, 2020
With source generator based implementation, will the difference between compiled and interpreted regex modes disappear; i.e. they both will endup using the same 'generated managed code' version?
danmoseley commentedon Nov 14, 2020
@am11 if you enable it, and presumably annotate appropriately.
There should already be no observable difference, other than perf.
am11 commentedon Nov 14, 2020
Great! In addition to annotations (or instead of, if low granularity is not required?), AppContext setting approach could also be useful here, for the end-users to specify project-wide choice.
svick commentedon Nov 15, 2020
@am11 I'm not sure it's feasible to make code that uses existing
Regexmembers use source generated implementation.stephentoub commentedon Nov 15, 2020
There are technically ways, but I currently don't think we should pursue them.
For example, imagine we added a static method like:
and then modified the Regex implementation to consult upon Regex construction a dictionary of registered runner factories populated by Register. That would enable any Regex usage to instead be backed by something provided via a Register call. Then in addition to the regex source generator spitting out the relevant types, it would also spit out a module initializer that called Register, e.g.
would be recognized by the source generator and would spit out:
where RegexRunner1Factory contains/references all the generated regex code.
But there are a variety of downsides to an approach like this, including but not limited to it bloating the IL with the regex code for all regexes and not just those a developer opted-in to having be source generated. I'm currently of the opinion it's better to opt-in on a case-by-case basis and have it be clear in the source what's resulting in a potentially huge amount of IL being spit into the binary.
danmoseley commentedon Nov 16, 2020
Note that the original proposal would require existing code to be refactored
In the latter case, there is currently an internal cache of Regex objects (by default 15) that is consulted by the static methods; by changing to the factory method you lose the benefit of that cache, but of course most of the purpose of that cache is to help avoid re-generating the IL, which would no longer happen anyway.
If it was important to avoid the refactorings above (eg., so that you could "warm up" the key regexes you plan to use in your startup code, say, without changing existing points of use) it would be easy to expose API for the generated code to add its Regex object to the existing cache (for static methods) or add its runner to a new RegexRunnerCache (for the instance methods, or possibly both). But I assume anyone opting into this is fine changing their existing points of use.
marek-safar commentedon Nov 16, 2020
@danmosemsft I don't have any customer evidence that this is an important scenario. You are correct that by default we run in non-SRE mode on all existing mobile configurations
danmoseley commentedon Nov 16, 2020
As for existing code that uses the .NET Framework ability to write out compiled regexes (like @mjsabby ) -- using CompileToAssembly -- that would have to be modified; instead of newing up the type derived from Regex, it would have to be refactored to use the factory method. I think that's fine -- I don't see any particular advantage of a type over a factory method, and an annotated partial method is more convenient way to hook into compilation.
stephentoub commentedon Nov 16, 2020
Which, for a scenario involving dynamically generating lots of regexes, might mean generating source that in turn relies on a source generator.
47 remaining items
stephentoub commentedon Sep 9, 2021
Exactly. So you rebuild, and you get the updated nuget. You rebuild, and you get the updated code gen from the compiler.
GrabYourPitchforks commentedon Sep 9, 2021
That sentence was very specifically referring to NuGet dependencies. The remainder of that paragraph discussed app developers who rely on shared runtime installs and who would not get source generator patches without recompiling & redeploying.
If you're making the argument that the runtime should be treated as a NuGet-style dependency and that applications should always recompile & redeploy when such a "dependency" has a vulnerability, I'm ok with that argument. But you'd need to square it with the fact that we do indeed allow (and sometimes promote!) shared installs, runtime redirection, and similar facilities.
stephentoub commentedon Sep 9, 2021
I'm making the argument that source generators are all about promoting run-time logic to build-time logic, and in doing so, you get all the benefits of that precomputation along with all the ramifications build-time decisions bring, whether it's code you manually wrote, code the C# compiler itself generates, code from a dependency selected at build time, code built into your binary from static linking, or code built into your binary from a source generator. The code from this or any other source generator is no longer part of the runtime.
GrabYourPitchforks commentedon Sep 10, 2021
Sure, that's a fair point. And perhaps this is primarily a discussion more geared for a proper threat modeling session.
In a nutshell, I guess it comes down to expectations. Say that we advertise this nifty new attribute as a performance win, that it gets wide acclaim and documented in tutorials and magazine articles. What expectation would our larger developer audience have with respect to how it works? If they're just slapping an attribute on a method, do they know that they're in effect performing static linking of runtime code?
If devs who apply this attribute properly understand all the ramifications it brings, then I think we've done due diligence and there's no need to implement my original request above. But judging from how our product is used, I think it would be a leap to say that the majority of our users who would apply these attributes really appreciate these ramifications. Or that they understand how it interplays with other systems (like shared runtime).
The logic I shared above is just one mechanism for solving this. And perhaps I'm unfairly pushing a little too hard on it. An alternative could be education and documentation. (Though docs are always a bit meh because it's often too easy to skip over them.) You clearly have given source generators considerable thought and may be forming some preliminary ideas for improving user experience. As this feature matures I'd be interested to see what proposals emerge.
bartonjs commentedon Sep 10, 2021
I don't know that I, personally, see the need for a runtime check for the generator version.
There's probably some room for developer education that, in general, running a source generator is somewhere between calling a library routine (bugs get fixed in dot-releases) and copying and pasting some code off of (e.g.) StackOverflow (where it's yours forever, no matter what problems it has), and maybe Regex warrants special notes because of how complex the output might be (since regular expressions as essentially a programming language).
sgen, and they have the same "the fix doesn't happen for free"For me, the followups would be
joperezr commentedon Dec 13, 2021
Regex source generator has now been part of the shared framework for a while now, and it's feature set is very likely on par compared to Regex.Compiled. @danmoseley is there any specific work that we want to additionally track with this issue, or can we go ahead and close this now?
danmoseley commentedon Dec 13, 2021
@joperezr I think it's fine to close this one. I assume any remaining work is tracked elsewhere. We should parent it under some common user story if it isn't already.
thanks for doing housekeeping.
joperezr commentedon Dec 14, 2021
Yup, I'm preparing that issue now, which is how I came to this one 😄