[.NET] 正規表現とコンパイルとパフォーマンス

[.NET] 正規表現とコンパイルとパフォーマンス

[.NET] 正規表現のコンパイルとパフォーマンス

.NET における正規表現はRegexクラスを利用します。Regexクラスには、静的メソッドとインスタンスメソッドそれぞれでパターンマッチングを行えます。またインスタンスメソッドを使う場合、Regex.OptionでCompiledを指定することで、1度コンパイルした正規表現を使いまわすことができます。

ここではいくつかの正規表現でのパターンマッチングのまとめと、パフォーマンスを比較します。

静的メソッド(Regex.IsMatchメソッド)

Regexクラスには静的メソッドとして、IsMatchメソッドが用意されています。引数に検査したい文字列と正規表現の文字列を渡すことで、検証結果(Bool)を返します。同様に、MatchメソッドやReplaceメソッドなども静的メソッドとして用意されています。

静的メソッドでは正規表現のパターン解析結果をキャッシュに保持します。キャッシュは保持できる個数が設定されており、既定値は15のようです。Regex.CacheSizeで取得・設定が可能です。

キャッシュの個数を0にすると、パターン解析結果を保持しなくなるので、処理速度が目に見えて遅くなります。

インスタンスメソッド

Regexクラスにはインスタンスメソッドに、静的メソッドと同じようにパターンマッチのメソッドが用意されており、同じように使用できます。インスタンスメソッドでの正規表現は、インスタンス生成時に正規表現のパターン解析を行い、インスタンス内部のデータとして保持します。

したがって、インスタンスを使ってパターンマッチングを行う限り、解析結果が使いまわされるため高速です。基本的にはキャッシュに保持している静的メソッドと同じような時間になると思うのですが、若干インスタンスメソッドのほうが高速です。ただしインスタンスの生成コストがあるので、静的メソッドのほうが有利な局面もあります。

コンパイル

Regexインスタンスを生成する際に、Regex.OptionでCompiledを指定して、生成時に正規表現のコンパイルを行うことができます。コンパイルを行うと、正規表現のパターン解析+IL生成まで行われます。これはインスタンスメソッドより生成時の処理コストが大きくなりますが、パターンマッチ実行時に高速になります。

パフォーマンスの比較

上記の3つのパフォーマンス(処理速度)を比較してみます。比較はメールアドレスっぽい正規表現を用意し、10種類の文字列*1000回、つまり1万回のパターンマッチを実行します。

実行結果

名称 時間
静的メソッド 00:00:00.2424866
インスタンスメソッド 00:00:00.1725770
コンパイルオプション 00:00:00.1114438

コンパイルオプションを指定したほうが高速という結果になりました。これは同じ正規表現を多数繰り返しているためだと思われます。複数の正規表現を使いまわす必要があれば、最適解も変わってきます。が、基本的にはインスタンスメソッドを使っておけばよいと思います。参考URLにコンパイルオプションで生成されたILは解放されないとありますし。以下検証コードを載せて終わります。

検証コード

class Program
{
    private static string[] Data = new string[] {
        "aaaaaaaaaaa_bbbb",
        "iGTrs@test.jp",
        "j_4jJR@sample.org",
        "Gqa5SI3dn1@sample.org",
        "ああああ@あいうえお.com",
        "aMwjKrVEfy@test.org",
        "nbrgUP@sample.co.jp",
        "dV6lrN@test.jp",
        "MH7VfpKwz@test.jp",
        "FCxYe7h@example.co.jp"
    };

    private const string Pattern = @"^([a-zA-Z0-9_/\.\-\?\+])+\@([a-zA-Z0-9]+[a-zA-Z0-9\-]*\.)+[a-zA-Z0-9\-]+$";

    private const int RepeatCount = 10000;

    public static void Main(string[] args)
    {
        MeasureTime_ClassReg();
        MeasureTime_InstanceReg();
        MeasureTime_CompiledReg();

        Console.ReadKey();
    }

    // クラスメソッドを使用した場合
    private static void MeasureTime_ClassReg()
    {
        //Regex.CacheSize = 0;
        //Console.WriteLine("Regex.CacheSize = {0}", Regex.CacheSize);
        //ストップウォッチを開始する
        var sw = new Stopwatch();
        sw.Start();

        for (int i = 0; i < RepeatCount; i++)
        {
            foreach (var item in Data)
            {
                Regex.IsMatch(item, Pattern);
            }
        }

        //結果を表示する
        sw.Stop();
        Console.WriteLine("Class:    {0}", sw.Elapsed);
    }

    // インスタンスメソッドを使用して
    private static void MeasureTime_InstanceReg()
    {
        //ストップウォッチを開始する
        var sw = new Stopwatch();
        sw.Start();

        var reg = new Regex(Pattern);
        for (int i = 0; i < RepeatCount; i++)
        {
            foreach (var item in Data)
            {
                reg.IsMatch(item);
            }
        }

        //結果を表示する
        sw.Stop();
        Console.WriteLine("Instance: {0}", sw.Elapsed);
    }

    // RegexOptions.Compiledを使用
    private static void MeasureTime_CompiledReg()
    {
        //ストップウォッチを開始する
        var sw = new Stopwatch();
        sw.Start();

        var reg = new Regex(Pattern, RegexOptions.Compiled);
        for (int i = 0; i < RepeatCount; i++)
        {
            foreach (var item in Data)
            {
                reg.IsMatch(item);
            }
        }

        //結果を表示する
        sw.Stop();
        Console.WriteLine("Compiled: {0}", sw.Elapsed);
    }
}
Class:    00:00:00.2424866
Instance: 00:00:00.1725770
Compiled: 00:00:00.1114438

参考

C#カテゴリの最新記事