RegexOptions.Compiledはどのように機能しますか?


169

正規表現をコンパイル対象としてマークすると、裏で何が起こっていますか?これはどのように比較されますか/キャッシュされた正規表現とは異なりますか?

この情報を使用して、パフォーマンスの向上と比較して計算のコストが無視できる場合をどのように判断しますか?


正規表現のベストプラクティスに関する優れたリソース:docs.microsoft.com/en-us/dotnet/standard/base-types/...
CADはやつ

回答:


302

RegexOptions.Compiled正規表現エンジンに、軽量コード生成(LCG)を使用して正規表現をILにコンパイルするように指示します。このコンパイルはオブジェクトの構築中に発生し、大幅に遅くなります。次に、正規表現を使用した一致が高速になります。

このフラグを指定しない場合、正規表現は「解釈済み」と見なされます。

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

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[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };

    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 | 4ミリ秒| 26ミリ秒| 31ミリ秒
解釈済み| x64 | 5 ms | 29ミリ秒| 35ミリ秒
コンパイル済み| x86 | 913 ms | 3775ミリ秒| 4487ミリ秒
コンパイル済み| x64 | 3300ミリ秒| 21985 ms | 22793ミリ秒

1,000,000一致-Regexオブジェクトの再利用

タイプ| プラットフォーム| 小数| 簡単なメールチェック| 外部メールチェック
-------------------------------------------------- ----------------------------
解釈済み| x86 | 422ミリ秒| 461ミリ秒| 2122ミリ秒
解釈済み| x64 | 436 ms | 463ミリ秒| 2167ミリ秒
コンパイル済み| x86 | 279ミリ秒| 166ミリ秒| 1268ミリ秒
コンパイル済み| x64 | 281ミリ秒| 176ミリ秒| 1180ミリ秒

これらの結果は、オブジェクトを再利用する場合、コンパイルされた正規表現が最大60%高速になる可能性があることを示していRegexます。ただし、場合によっては、構築が3桁以上遅くなることがあります。

また、.NET のx64バージョンは、正規表現のコンパイルに関して5〜6倍遅くなる可能性があることも示しています。


どちらかが当てはまる場合は、コンパイルされたバージョン使用することをお勧めします

  1. オブジェクトの初期化コストは気にせず、パフォーマンスをさらに向上させる必要があります。(ここではミリ秒の端数を話していることに注意してください)
  2. 初期化コストを少し気にしますが、Regexオブジェクトを何度も再利用しているため、アプリケーションのライフサイクル中にそれを補います。

作業中のスパナ、正規表現キャッシュ

正規表現エンジンには、Regexクラスの静的メソッドを使用してテストされた最新の15個の正規表現を保持するLRUキャッシュが含まれています。

たとえばRegex.ReplaceRegex.Matchなど。すべてRegexキャッシュを使用します。

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

新しい正規表現は、Regexクラスの静的ヘルパーによってのみキャッシュさます。オブジェクトを構築する場合、キャッシュがチェックされます(再利用およびバンプのため)。ただし、構築する正規表現はキャッシュに追加されません

このキャッシュは簡単な 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のいくつかの予想される変更があります。


11
すばらしい答えです。私自身の目的でCompiled、静的な(アプリケーション全体の)Regexオブジェクトを実際に格納しているWebサイトのコードでよく使用します。したがって、RegexIISがアプリケーションを起動するときに一度だけ構築し、数千回再利用する必要があります。アプリケーションが頻繁に再起動しない限り、これはうまく機能します。
スティーブウォーサム

W00!この情報は、プロセスを8〜13時間から最大30分に短縮するのに役立ちました。ありがとうございました!
ロバートキリスト

3
すばらしい回答サム、バージョン4.5での変更点について更新できますか?(私はあなたがしばらく前にスタックを変更したことを知っています...)
gdoronはモニカをサポートしています

@gdoronissupportingMonica NET 5.0では、Regexのパフォーマンスが向上しています。これに関するブログ投稿を見ました。ここで
kapozade

42

BCLチームブログのこのエントリは、「正規表現のパフォーマンス」というすばらしい概要を提供します。

要するに、3つのタイプの正規表現があります(それぞれが前のものより速く実行されます):

  1. 解釈された

    オンザフライで高速に作成し、実行に時間がかかる

  2. コンパイル済み(あなたが尋ねているように見えるもの)

    オンザフライで作成するのが遅く、実行するのが速い(ループでの実行に適しています)

  3. コンパイル済み

    アプリのコンパイル時に作成(実行時の作成ペナルティなし)、高速実行

したがって、正規表現を1回だけ実行する場合、またはアプリのパフォーマンスクリティカルではないセクション(つまり、ユーザー入力の検証)で実行する場合は、オプション1で問題ありません。

ループで正規表現を実行する場合(つまり、ファイルの行ごとの解析)、オプション2を使用する必要があります。

アプリで変更されない正規表現が多く、使用頻度が高い場合は、オプション3を使用できます。


1
番号3は、カスタムroslynを使用して簡単にできますCompileModule。くそー、私は新しいプラットフォームをさらに詳しく見る必要があります。
クリスチャンゴルハート2017年

9

.NET 2.0以降の正規表現のパフォーマンスは、コンパイルされていない正規表現のMRUキャッシュによって改善されていることに注意してください。Regexライブラリコードは、毎回同じコンパイルされていない正規表現を再解釈しなくなりました。

だから、潜在的に大きなパフォーマンスがありペナルティコンパイルされ、その場での正規表現で。読み込み時間が遅くなるだけでなく、システムはより多くのメモリを使用して正規表現をコンパイルしてオペコードにします。

基本的に、現在のアドバイスは、正規表現をコンパイルしないか、別のアセンブリに事前にコンパイルすることです。

参照:BCLチームブログの正規表現のパフォーマンス[David Gutierrez]



0

以下のコードがre.compile関数の概念を理解するのに役立つことを願っています

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)

回答ありがとうございます。コードはPython言語です。問題は、Microsoft .NETフレームワークのRegexOptions.Compiledオプションに関するものでした。あなたは見ることができます[ .NET ]タグ質問の下に添付します。
ストーマ

あぁ、そうです!ストーマに感謝
ダニエル・ムトゥパンディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.