Skip to content

Commit

Permalink
Delay JIT'ing until first execution
Browse files Browse the repository at this point in the history
I'd previously removed multiple delegate allocations that were being incurred every time we needed to run a compiled regex concurrently.  In doing so, however, I inadvertently forced JIT'ing to occur when the Regex was created rather than on first use; that can in turn lead to more start-up overheads for regexes that are created on startup (e.g. to initialize a static field) but that aren't used at start-up, as well as reduce potential parallelism that could otherwise occur for distinct regexes being run for the first time concurrently.  This addresses the issue by still caching delegates, but in addition to rather than instead of the DynamicMethod objects.
  • Loading branch information
stephentoub committed Jan 9, 2020
1 parent 8019c07 commit 40d550f
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,32 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// This is the only concrete implementation of RegexRunnerFactory,
// but we cannot combine them due to RegexRunnerFactory having shipped public.
using System.Reflection.Emit;

namespace System.Text.RegularExpressions
{
internal sealed class CompiledRegexRunnerFactory : RegexRunnerFactory
{
private readonly Action<RegexRunner> _go;
private readonly Func<RegexRunner, bool> _findFirstChar;
private readonly Action<RegexRunner> _initTrackCount;
private readonly DynamicMethod _goMethod;
private readonly DynamicMethod _findFirstCharMethod;
private readonly DynamicMethod _initTrackCountMethod;

public CompiledRegexRunnerFactory(Action<RegexRunner> go, Func<RegexRunner, bool> findFirstChar, Action<RegexRunner> initTrackCount)
// Delegates are lazily created to avoid forcing JIT'ing until the regex is actually executed.
private Action<RegexRunner>? _go;
private Func<RegexRunner, bool>? _findFirstChar;
private Action<RegexRunner>? _initTrackCount;

public CompiledRegexRunnerFactory(DynamicMethod goMethod, DynamicMethod findFirstCharMethod, DynamicMethod initTrackCountMethod)
{
_go = go;
_findFirstChar = findFirstChar;
_initTrackCount = initTrackCount;
_goMethod = goMethod;
_findFirstCharMethod = findFirstCharMethod;
_initTrackCountMethod = initTrackCountMethod;
}

protected internal override RegexRunner CreateInstance() => new CompiledRegexRunner(_go, _findFirstChar, _initTrackCount);
protected internal override RegexRunner CreateInstance() =>
new CompiledRegexRunner(
_go ??= (Action<RegexRunner>)_goMethod.CreateDelegate(typeof(Action<RegexRunner>)),
_findFirstChar ??= (Func<RegexRunner, bool>)_findFirstCharMethod.CreateDelegate(typeof(Func<RegexRunner, bool>)),
_initTrackCount ??= (Action<RegexRunner>)_initTrackCountMethod.CreateDelegate(typeof(Action<RegexRunner>)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ namespace System.Text.RegularExpressions
{
internal sealed class RegexLWCGCompiler : RegexCompiler
{
private static int s_regexCount = 0;
private static readonly Type[] s_paramTypes = new Type[] { typeof(RegexRunner) };
private static int s_regexCount = 0;

/// <summary>The top-level driver. Initializes everything then calls the Generate* methods.</summary>
public RegexRunnerFactory FactoryInstanceFromCode(RegexCode code, RegexOptions options, bool hasTimeout)
Expand All @@ -32,16 +32,13 @@ public RegexRunnerFactory FactoryInstanceFromCode(RegexCode code, RegexOptions o
DynamicMethod goMethod = DefineDynamicMethod("Go" + regexnumString, null, typeof(CompiledRegexRunner));
GenerateGo();

DynamicMethod firstCharMethod = DefineDynamicMethod("FindFirstChar" + regexnumString, typeof(bool), typeof(CompiledRegexRunner));
DynamicMethod findFirstCharMethod = DefineDynamicMethod("FindFirstChar" + regexnumString, typeof(bool), typeof(CompiledRegexRunner));
GenerateFindFirstChar();

DynamicMethod trackCountMethod = DefineDynamicMethod("InitTrackCount" + regexnumString, null, typeof(CompiledRegexRunner));
DynamicMethod initTrackCountMethod = DefineDynamicMethod("InitTrackCount" + regexnumString, null, typeof(CompiledRegexRunner));
GenerateInitTrackCount();

return new CompiledRegexRunnerFactory(
(Action<RegexRunner>)goMethod.CreateDelegate(typeof(Action<RegexRunner>)),
(Func<RegexRunner, bool>)firstCharMethod.CreateDelegate(typeof(Func<RegexRunner, bool>)),
(Action<RegexRunner>)trackCountMethod.CreateDelegate(typeof(Action<RegexRunner>)));
return new CompiledRegexRunnerFactory(goMethod, findFirstCharMethod, initTrackCountMethod);
}

/// <summary>Begins the definition of a new method (no args) with a specified return value.</summary>
Expand Down

0 comments on commit 40d550f

Please sign in to comment.