2009-01-09 39 views
8

这只是一个问题,以满足我的好奇心。 但对我来说这很有趣。为什么缓存的Regexp超越编译好的版本?

我写了这个简单的基准。它以几千次的随机顺序调用Regexp执行的3个变体:

基本上,我使用相同的模式,但以不同的方式。

  1. 您的普通方式没有任何RegexOptions。从.NET 2.0开始,这些不会被缓存。但应该“缓存”,因为它保存在一个非常全球的范围内,而不是重置。

  2. 随着RegexOptions.Compiled

  3. 随着到静态Regex.Match(pattern, input)调用它不会在.NET 2.0

这里获取缓存是代码:

static List<string> Strings = new List<string>();   
static string pattern = ".*_([0-9]+)\\.([^\\.])$"; 

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: 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 

所以你有它。不多,但约7-8%的差异。

这不是唯一的秘密。我无法解释为什么第一种方法会慢得多,因为它不会被重新评估,而是被保存在一个全局静态变量中。

顺便说一句,这是在.net 3.5和Mono 2.2上表现完全一样。在Windows上。

那么,任何想法,为什么编译的变体甚至会落后?

EDIT1:

 
    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 

还几乎废止所有的其他问题还有:

固定码结果现在这个样子了。

感谢您的答案。

回答

4

在Regex.Match版本中,您正在寻找模式中的输入。尝试交换周围的参数。

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

I noticed类似的行为。我也想知道为什么编译版本会变慢,但是注意到在一定数量的调用之上,编译版本更快。所以我挖了一点Reflector,我注意到对于一个编译的正则表达式,仍然有一点设置是在第一次调用时执行的(具体来说,创建一个合适的对象实例)。

在我的测试中,我发现如果我将构造函数和最初的throw-away调用移动到定时器启动之外的正则表达式中,编译的正则表达式无论我运行了多少次迭代都会赢。


顺便说一句,使用静态Regex方法时,该框架是做缓存使用静态Regex方法时唯一需要的优化。这是因为每次调用静态方法Regex时都会创建一个新的Regex对象。在Regex类的构造函数中,它必须解析模式。缓存允许静态方法的后续调用重用从第一次调用解析的RegexTree,从而避免解析步骤。

当您在单个Regex对象上使用实例方法时,这不是问题。解析仍然只执行一次(当您创建对象时)。另外,你可以避免运行构造函数中的所有其他代码,以及堆分配(和后续的垃圾回收)。

马丁布朗noticed你扭转了你的静态Regex调用的参数(好赶,马丁)。我认为你会发现,如果你解决这个问题,实例(未编译)的正则表达式将每次都打败静态调用。你也应该发现,鉴于我上面的发现,编译的实例也会击败未编译的实例。

但是:在对所创建的每个正则表达式盲目应用该选项之前,您应该在编译的正则表达式上真正阅读Jeff Atwood's post

+0

谢谢你的解释。 在我的情况下,最初的步骤似乎不会导致很多成本(请参阅新结果)。 我在发布之前阅读了Jeff Atwood的帖子。所以我知道这些缺点。在我的情况下,编译选项会有所帮助,虽然在标准用例中没有那么多。 – user51710 2009-01-09 15:15:52

+0

** Jeff Atwood的帖子已经移到:[编译或不编译*(2005年3月3日)*](http://blog.codinghorror.com/to-compile-or-not-to-compile/) – DavidRR 2014-04-10 17:39:43

0

如果您经常使用相同的模式匹配相同的字符串,这可以解释为什么缓存版本比编译版本稍快。

0

这是来自文档;

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

静态正则表达式方法被调用并且常规 表达式不能在高速缓存中找到,正则表达式引擎 正则表达式转换成一组操作码和他们在缓存中存储 。然后它将这些操作代码转换为MSIL,以便JIT编译器可以执行它们。 解释常规 表达式以较慢的执行时间为代价减少启动时间。 正因为如此,它们是当正则表达式是在一个小的数目的方法的使用最好 用于调用,或者如果 调用正则表达式的方法的确切数目是未知的,但预期是 小。随着方法调用次数的增加,启动时间缩短后的性能增益 超过了执行速度较慢的执行速度。

相反解释的正则表达式,编译的正则表达式 增加启动时间,但执行个人 模式匹配方法快。因此,编译正则表达式导致的性能优势 与调用的正则表达式方法的数量成比例地增加了 。


总之,我们建议您使用解释的正则表达式,当你调用一个特定 正则表达式的正则表达式的方法相对较少。

,当你调用定期 表达方式与特定的正则表达式相对 频繁您应该使用编译的正则表达式


如何检测?

确切阈处的 解释的正则表达式的更慢的执行速度大于从它们的还原 启动时间增益,或在其中的 编译的正则表达式的慢启动时间超过从它们更快 执行增益阈值速度,很难确定。它取决于多种因素,包括正则表达式的复杂性和它处理的特定数据。 要确定编译的正则表达式是否为您的 特定应用场景提供最佳性能,可以使用秒表类 比较其执行时间


编译的正则表达式:

我们建议您编译正则表达式的组件 以下几种情况:

  1. 如果你是谁想要 组件开发创建一个可重复使用的正则表达式库。
  2. 如果您期望 您的正则表达式的模式匹配方法被称为 不确定的次数 - 任何地方从一次或两次到 成千上万或几万次。与编译的或 解释的正则表达式不同,编译为 以分离程序集的正则表达式提供的性能是一致的,无论方法调用的数量是多少 。
相关问题