1. ホーム
  2. .net

[解決済み] RegexOptions.Compiledはどのように動作するのですか?

2022-04-29 02:13:49

質問

正規表現をコンパイルするものとしてマークするとき、舞台裏では何が起こっているのでしょうか?これはキャッシュされた正規表現とどのように違うのですか?

この情報を使って、計算のコストが性能の向上と比較して無視できる場合はどのように判断するのですか?

どのように解決するのか?

RegexOptions.Compiled は正規表現エンジンに対して、軽量コード生成( LCG ). このコンパイルは、オブジェクトの構築時に行われ 大きく が遅くなる。その代わり、正規表現を使ったマッチはより高速になります。

このフラグを指定しない場合、正規表現は "unterplied"とみなされます。

この例を見てみましょう。

public static void TimeAction(string description, int times, Action func)
{
    // warmup
    func();

    var watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < times; i++)
    {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}

static void Main(string[] args)
{
    var simple = "^\\d+$";
    var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
    var complex = @"^(([^<>()[\]\\.,;:\s@""]+"
      + @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
      + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
      + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
      + @"[a-zA-Z]{2,}))$";


    string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
    string[] emails = new string[] { "[email protected]", "sss@s", "[email protected]", "[email protected]" };

    foreach (var item in new[] {
        new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
        new {Pattern = medium, Matches = emails, Name = "Simple email match"},
        new {Pattern = complex, Matches = emails, Name = "Complex email match"}
    })
    {
        int i = 0;
        Regex regex;

        TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        i = 0;
        TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern, RegexOptions.Compiled);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern);
        i = 0;
        TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern, RegexOptions.Compiled);
        i = 0;
        TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

    }
}

3種類の正規表現で4つのテストを実行します。最初に シングル コンパイル済みと非コンパイル済みとで、1度だけマッチします。次に、同じ正規表現を再利用する繰り返しマッチをテストします。

私のマシンでの結果 (リリースでコンパイル、デバッガは未装着)

1000件のシングルマッチ (正規表現、マッチ、破棄の構築)

タイプ|プラットフォーム|トリビアルナンバー|シンプルなメールチェック|エクストメールチェック
------------------------------------------------------------------------------
インタプリタ|x86|4ms|26ms|31ms
インタープリティッド|x64|5ms|29ms|35ms
コンパイル|x86|913ms|3775ms|4487ms
コンパイル時|x64|3300ms|21985ms|22793ms

1,000,000マッチ - Regexオブジェクトの再利用

種類|プラットフォーム|些細な数|シンプルなメールチェック|エクストメールチェック
------------------------------------------------------------------------------
インタープリタ|x86|422ms|461ms|2122ms
インタープリティッド|x64|436ms|463ms|2167ms
コンパイル|x86|279ms|166ms|1268ms
コンパイル時|x64|281ms|176ms|1180ms

これらの結果から、コンパイルされた正規表現では、最大で 60% を再利用する場合、より高速になります。 Regex オブジェクトを作成します。 しかし を超える場合があります。 3桁 の方が遅くなります。

ということも示しています。 x64版 の.NETは 5~6倍遅くなる 正規表現のコンパイルに関して


推奨されるのは コンパイルされたバージョンを使用する のどちらかである場合

  1. オブジェクトの初期化コストを気にせず、余分なパフォーマンスブーストを必要とする。(ここでは1ミリ秒の単位で話していることに注意)
  2. 初期化コストは多少気になるが、REGEXオブジェクトを何度も再利用するため、アプリケーションのライフサイクルの中でそれを補うことができる。

Regexキャッシュにスパナを刺す

正規表現エンジンには、LRUキャッシュが含まれており、このキャッシュには Regex クラスがあります。

例えば Regex.Replace , Regex.Match などは、すべてRegexキャッシュを使用します。

キャッシュのサイズは Regex.CacheSize . アプリケーションのライフサイクルの中で、いつでもサイズの変更を受け入れることができます。

新しい正規表現はキャッシュされるだけ 静的ヘルパーによって を使用します。オブジェクトを作成すると、キャッシュがチェックされます (再利用とバンプが行われます)。 キャッシュに追加されない .

このキャッシュは トリビアル LRUキャッシュは、単純なダブルリンクリストを使って実装されています。たまたまそれを5000に増やし、静的ヘルパーで5000の異なる呼び出しを使用する場合、すべての正規表現の構築は以前にキャッシュされたかどうかを確認するために5000のエントリをクロールします。があります。 ロック を使用するため、チェックによって並列性が低下し、スレッドブロッキングが発生する可能性があります。

このようなケースから身を守るために、かなり低い数値に設定されていますが、場合によっては増やさざるを得ないこともあるでしょう。

私の 強い推奨 となります。 決して を渡すと RegexOptions.Compiled オプションを静的ヘルパーに追加します。

例えば

\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)

というのも、LRUキャッシュのミスが原因で 超高価な をコンパイルします。さらに、依存するライブラリが何をしているのか分からないので 可能な限り キャッシュのサイズ

こちらもご覧ください。 BCLチームブログ


備考 : これは.NET 2.0と.NET 4.0に関連しています。4.5で予想されるいくつかの変更により、これが修正される可能性があります。