キャッシュされた正規表現がコンパイルされた正規表現よりも優れているのはなぜですか?

StackOverflow https://stackoverflow.com/questions/428196

質問

これは私の好奇心を満たすための質問です。 しかし、私にとっては興味深いです。

この簡単なベンチマークを作成しました。 Regexp実行の3つのバリアントをランダムな順序で数千回呼び出します。

基本的に、同じパターンを異なる方法で使用しています。

  1. RegexOptions を使用しない通常の方法。 .NET 2.0以降では、これらはキャッシュされません。ただし、「キャッシュ」する必要があります。かなりグローバルなスコープに保持されており、リセットされないためです。

  2. RegexOptions.Compiled

  3. を使用
  4. .NET 2.0でキャッシュされる静的な Regex.Match(pattern、input)の呼び出しで

コードは次のとおりです:

static List<string> Strings = new List<string>();        
static string pattern = ".*_([0-9]+)\\.([^\\.])
    Not compiled and not automatically cached:
    Total milliseconds: 6185,2704
    Adjusted milliseconds: 6185,2704

    Compiled and not automatically cached:
    Total milliseconds: 2562,2519
    Adjusted milliseconds: 2551,56949184038

    Not compiled and automatically cached:
    Total milliseconds: 2378,823
    Adjusted milliseconds: 2336,3187176891
quot;; static Regex Rex = new Regex(pattern); static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled); static Random Rand = new Random(123); static Stopwatch S1 = new Stopwatch(); static Stopwatch S2 = new Stopwatch(); static Stopwatch S3 = new Stopwatch(); static void Main() { int k = 0; int c = 0; int c1 = 0; int c2 = 0; int c3 = 0; for (int i = 0; i < 50; i++) { Strings.Add("file_" + Rand.Next().ToString() + ".ext"); } int m = 10000; for (int j = 0; j < m; j++) { c = Rand.Next(1, 4); if (c == 1) { c1++; k = 0; S1.Start(); foreach (var item in Strings) { var m1 = Rex.Match(item); if (m1.Success) { k++; }; } S1.Stop(); } else if (c == 2) { c2++; k = 0; S2.Start(); foreach (var item in Strings) { var m2 = RexCompiled.Match(item); if (m2.Success) { k++; }; } S2.Stop(); } else if (c == 3) { c3++; k = 0; S3.Start(); foreach (var item in Strings) { var m3 = Regex.Match(item, pattern); if (m3.Success) { k++; }; } S3.Stop(); } } Console.WriteLine("c: {0}", c1); Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("c: {0}", c2); Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString()); Console.WriteLine("c: {0}", c3); Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString()); }

私がそれを呼び出すたびに、結果は次のようになります:

    Not compiled and not automatically cached:
    Total milliseconds: 6456,5711
    Adjusted milliseconds: 6456,5711

    Compiled and not automatically cached:
    Total milliseconds: 2668,9028
    Adjusted milliseconds: 2657,77574842168

    Not compiled and automatically cached:
    Total milliseconds: 6637,5472
    Adjusted milliseconds: 6518,94897724836

これでおしまいです。それほどではありませんが、約7〜8%の違いです。

それが唯一の謎ではありません。再評価されることはなく、グローバルな静的変数に保持されるため、最初の方法がなぜそれほど遅くなるのか説明できません。

ところで、これは.Net 3.5とMono 2.2にあり、まったく同じ動作をします。 Windowsの場合。

では、アイデア、コンパイルされたバリアントがなぜ遅れるのでしょうか

EDIT1:

コードを修正すると、結果は次のようになります。

<*>

これは、他のすべての質問もほぼ廃止します。

回答ありがとうございます。

役に立ちましたか?

解決

Regex.Matchバージョンでは、パターンの入力を探しています。パラメータを入れ替えてみてください。

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct

他のヒント

気づいた動作。また、コンパイルされたバージョンがなぜ遅くなるのか疑問に思いましたが、一定の呼び出し回数を超えると、コンパイルされたバージョンが高速になることに気付きました。そこで Reflector を少し掘り下げましたが、コンパイル済みの正規表現では、最初の呼び出しで実行される小さなセットアップがまだあります(具体的には、適切な RegexRunner オブジェクト)。

テストでは、コンストラクターと、タイマースタートの外側で正規表現への最初のスローアウェイコールの両方を移動した場合、実行した反復回数に関係なく、コンパイル済みの正規表現は勝ちません。


ちなみに、静的な Regex メソッドを使用するときにフレームワークが実行するキャッシュは、静的な Regex メソッドを使用するときにのみ必要な最適化です。これは、静的な Regex メソッドを呼び出すたびに新しい Regex オブジェクトが作成されるためです。 Regex クラスのコンストラクターでは、パターンを解析する必要があります。キャッシングにより、静的な Regex メソッドの後続の呼び出しで、最初の呼び出しから解析された RegexTree を再利用できるため、解析ステップが回避されます。

単一の Regex オブジェクトでインスタンスメソッドを使用する場合、これは問題ではありません。解析はまだ1回だけ実行されます(オブジェクトを作成するとき)。さらに、コンストラクタ内の他のすべてのコード、およびヒープ割り当て(および後続のガベージコレクション)の実行を回避できます。

Martin Brown 注目静的な Regex 呼び出しの引数を逆にしました(良いキャッチ、Martin)。これを修正すると、インスタンス(コンパイルされていない)正規表現が毎回静的呼び出しに勝つことに気付くと思います。また、上記の私の調査結果を考えると、コンパイルされたインスタンスもコンパイルされていないインスタンスに勝ることに気付くはずです。

しかし:あなたは本当に作成したすべての正規表現に盲目的にそのオプションを適用する前に、コンパイルされた正規表現に関するジェフアトウッドの投稿

同じパターンを使用して同じ文字列に常に一致する場合、キャッシュされたバージョンがコンパイルされたバージョンよりもわずかに速い理由を説明できる場合があります。

これはドキュメントからのものです。

https://msdn.microsoft.com /en-us/library/gg578045(v=vs.110).aspx

  

静的な正規表現メソッドが呼び出され、   式がキャッシュに見つかりません、正規表現エンジン   正規表現をオペレーションコードとストアのセットに変換します   キャッシュに保存します。次に、これらのオペレーションコードをMSILに変換します。   JITコンパイラーがそれらを実行できること。 通訳付き   式を使用すると、実行時間が遅くなりますが、起動時間が短縮されます。   このため、正規表現が   少数のメソッド呼び出しで使用される、または   正規表現メソッドの呼び出しは不明ですが、   小さい。メソッド呼び出しの数が増えると、パフォーマンスが向上します   起動時間が短縮されたことから、実行が遅くなります   スピード。

     

解釈された正規表現とは対照的に、コンパイルされた正規表現   式は起動時間を増加させますが、個別に実行します   パターンマッチング方法の高速化。結果として、パフォーマンス上の利点   正規表現をコンパイルした結果、   呼び出される正規表現メソッドの数に比例。


  

要約すると、特定の   正規表現は比較的まれです。

     

regularを呼び出すときは、コンパイルされた正規表現を使用する必要があります   特定の正規表現を相対的に持つ表現メソッド   頻繁に。


検出方法

  

より遅い実行速度の正確なしきい値   解釈された正規表現は、それらの減少からの利益を上回る   起動時間、または起動時間が遅くなるしきい値   コンパイルされた正規表現は、より高速であるため、   実行速度、決定することは困難です。それは様々に依存します   正規表現の複雑さや   処理する特定のデータ。 解釈するかどうかを判断するには   コンパイルされた正規表現は、あなたに最高のパフォーマンスを提供します   特定のアプリケーションシナリオでは、Stopwatchクラスを使用して   実行時間を比較します


コンパイルされた正規表現:

  

正規表現をアセンブリにコンパイルすることをお勧めします   次の状況:

     
      
  1. あなたが望むコンポーネント開発者なら   再利用可能な正規表現のライブラリを作成します。
  2.   
  3. あなたが期待するなら   正規表現のパターンマッチングメソッドと呼ばれる   不定回数-1回または2回から   数千または数万回。コンパイル済みまたは   解釈された正規表現、コンパイルされた正規表現   アセンブリを分離することにより、一貫したパフォーマンスを   メソッド呼び出しの数。
  4.   
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top