net企业网站源码分享 简洁企业网站源码

大家好,今天来为大家分享net企业网站源码分享的一些知识点,和简洁企业网站源码的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!

前言

前两篇文章part1和part2基本上理清了IsSplitter()运行缓慢的原因——在函数内部使用了带Compile选项的正则表达式。

但是没想到在IsSplitter()内部使用不带Compiled选项的正则表达式,整个程序运行起来非常快,跟静态函数版本的运行速度不相上下。又有了如下疑问:

为什么使用不带Compiled选项实例化的Regex速度会这么快?为什么把Regex变量从局部改成全局变量后运行速度有了极大提升?除了避免重复实例化,还有哪些提升?为什么PerfView收集到的采样数据,大部分发生在MatchCollections.Count内部,极少发生在Regex的构造函数内部?(使用带Compiled选项的正则表达式的时候)Regex.IsMatch()是如何使用缓存的?直接实例化的Regex对象会使用正则表达式引擎内部的缓存吗?正则表达式引擎内部根据什么缓存的?什么时候会生成动态方法?生成的动态方法是在哪里调用的?

本文会继续使用Perfview抓取一些关键数据进行分析,有些疑问需要到.NET源码中寻找答案。在查看代码的过程中,发现有些逻辑单纯看源码不太容易理解,于是又调试跟踪了.NET中正则表达式相关源码。由于篇幅原因,本篇不会介绍如何下载.NET源码,如何调试.NET源码的方法。但是会单独写一篇简单的介绍文章。

解惑

为什么使用不带Compiled选项实例化的Regex速度会这么快?还是使用PerfView采集性能数据并分析,如下图:可以发现,IsSplitter()函数只在第一次被调用时发生了一次JIT,后续调用耗时不到0.1ms(图中最后一次调用耗时:4090.629-4090.597=0.032ms)。使用带Compiled选项实例化的Regex的IsSplitter()函数,如下图:view-filter-event-with-etwlogger每次调用大概要消耗11ms(5616.375-5604.637=11.738ms)。至于为什么不带Compiled选项的正则表达式在调用过程中没有多余的JIT,与疑问7一起到源码中找答案。为什么把Regex变量从局部改成全局变量后运行速度有了极大提升?除了避免重复实例化,还有哪些提升?修改代码,把局部变量改成全局变量,编译。再次使用PerfView采集性能数据并分析,如下图:可以发现与使用不带Compiled选项的局部变量版本一样,只发生了一次JIT。所以把局部变量改成全局变量后,除了避免了重复实例化的开销(很小),更重要的是避免了多余的JIT操作。为什么PerfView收集到的采样数据,大部分发生在MatchCollections.Count内部,极少发生在Regex的构造函数内部?(使用带Compiled选项的正则表达式的时候)Regex构造函数只被JIT了一次,后面的调用都是在执行原生代码,执行速度非常快。而MatchCollections.Count每次执行的时候都需要执行JIT(每次都需要10ms以上),所以大部分数据在MatchCollections.Count内部,是非常合理的。Regex.IsMatch()是如何使用缓存的?Regex.IsMatch()有很多重载版本,最后都会调用下面的版本:staticboolIsMatch(Stringinput,Stringpattern,RegexOptionsoptions,TimeSpanmatchTimeout){returnnewRegex(pattern,options,matchTimeout,true).IsMatch(input);}该函数会在内部构造一个临时的Regex对象,并且构造函数的最后一个参数useCaChe的值是true,表示使用缓存。

疑问5疑问6的答案在Regex的构造函数中,先看看Regex的构造函数。

Regex构造函数

Regex有很多个构造函数,列举如下:

publicRegex(Stringpattern)\n:this(pattern,RegexOptions.None,DefaultMatchTimeout,false){}\n\npublicRegex(Stringpattern,RegexOptionsoptions)\n:this(pattern,options,DefaultMatchTimeout,false){}\n\nRegex(Stringpattern,RegexOptionsoptions,TimeSpanmatchTimeout)\n:this(pattern,options,matchTimeout,false){}\n

注意:以上构造函数的最后一个参数都是false,表示不使用缓存。

这些构造函数最后都会调用下面的私有构造函数(代码有所精简调整):

privateRegex(Stringpattern,RegexOptionsoptions,TimeSpanmatchTimeout,booluseCache)\n{\nstringcultureKey=null;\nif((options&RegexOptions.CultureInvariant)!=0)\ncultureKey=CultureInfo.InvariantCulture.ToString();//&34;\nelse\ncultureKey=CultureInfo.CurrentCulture.ToString();\n\n//构造缓存用到的key,包含options,culture和pattern\nStringkey=((int)options).ToString(NumberFormatInfo.InvariantInfo)+&34;+cultureKey+&34;+pattern;\nCachedCodeEntrycached=LookupCachedAndUpdate(key);\n\nthis.pattern=pattern;\nthis.roptions=options;\n\nif(cached==null){\n//如果没找到缓存就生成类型为RegexCodes的code,包含了字节码等信息\nRegexTreetree=RegexParser.Parse(pattern,roptions);\ncode=RegexWriter.Write(tree);\n//如果指定了useCache参数就缓存起来,下次就能在缓存中找到了\nif(useCache)\ncached=CacheCode(key);\n}else{\n//如果找到了缓存就使用缓存中的信息\ncode=cached._code;\nfactory=cached._factory;\nrunnerref=cached._runnerref;\n}\n\n//如果指定了Compiled选项,并且factory是空(没使用缓存,或者缓存中的_factory是空)\nif(UseOptionC()&&factory==null){\n//根据code和roptions生成factory\nfactory=Compile(code,roptions);\n\n//需要缓存就缓存起来\nif(useCache&&cached!=null)\ncached.AddCompiled(factory);\n}\n}\n

注意:带booluseCache标记的构造函数是私有的,也就是说不能直接使用此构造函数实例化Regex。

首先会根据option+culture+pattern到缓存中查找。如果没找到缓存就生成类型为RegexCodes的code(包含了字节码等信息),如果找到了缓存就使用缓存中的信息。如果指定了Compiled选项(UseOptionC()会返回true),并且factory是空(没使用缓存或者缓存中的_factory是空),就会执行Compile()函数,并把返回值保存到factory成员中。

至此,可以回答第56两个疑问了。

直接实例化的Regex对象会使用正则表达式引擎内部的缓存吗?会优先根据option+culture+pattern到缓存中查找,但是否更新缓存是由最后一个参数useCache决定的,与是否指定Compiled选项无关。正则表达式引擎内部根据什么缓存的?根据option+culture+pattern缓存。

疑问7与由疑问1引申出来的JIT问题是一个问题。之所以会JIT,是因为有需要JIT的代码,如果不断有新的动态方法产生出来并执行,那么就需要不断地JIT。由于此问题涉及到的代码量比较大,逻辑比较复杂,需要深入.NET源码进行查看。为了更好的理解整个过程,我简单梳理了IsSpitter()函数中涉及到的关键类以及类之间的关系,整理成下图,供参考。

流程&类关系梳理

看完上图后,可以继续看剩下的JIT问题了。因为大多数JIT都出现在MatchCollection.Count中,可以由此切入。

MatchCollection.Count

实现代码如下:

publicintCount{\nget{\nif(_done)\nreturn_matches.Count;\nGetMatch(infinite);\nreturn_matches.Count;\n}\n}\n

Count会调用GetMatch()函数,而GetMatch()函数会不断调用_regex.Run()函数。

_regex是哪来的呢?在构造MatchCollection实例时传过来的。

MatchCollection是由Regex.Matches()实例化的,代码如下(去掉了判空逻辑):

publicMatchCollectionMatches(Stringinput,intstartat){\nreturnnewMatchCollection(this,input,0,input.Length,startat);\n}\n

该函数会实例化一个MatchCollection对象,并把当前Regex实例作为第一个参数传给MatchCollection的构造函数。该参数会被保存到MatchCollection实例的_regex成员中。

接下来继续查看Regex.Run函数的实现。

Regex.Run()

具体实现代码如下(代码有精简):

internalMatchRun(boolquick,intprevlen,Stringinput,intbeginning,intlength,intstartat){\nMatchmatch;\n//使用缓存的时候,可能从缓存中拿到一个有效的runner,其它情况下都是null。\nRegexRunnerrunner=(RegexRunner)runnerref.Get();\n\n//不使用缓存的时候runner是null\nif(runner==null){\n//如果factory不为空就通过factory创建一个runner。\n//使用了Compiled标志创建的Regex实例的factory不为空\nif(factory!=null)\nrunner=factory.CreateInstance();\nelse\nrunner=newRegexInterpreter(code,UseOptionInvariant()?CultureInfo.InvariantCulture:CultureInfo.CurrentCulture);\n}\n\ntry{\n//调用RegexRunner.Scan扫描匹配项。\nmatch=runner.Scan(this,input,beginning,beginning+length,startat,prevlen,quick,internalMatchTimeout);\n}finally{\nrunnerref.Release(runner);\n}\n\nreturnmatch;\n}\n

逻辑还是非常清晰的,先找到或者创建(通过factory.CreateInstance()或者直接new)一个类型为RegexRunner实例runner,然后调用runner->Scan()进行匹配。

对于使用Compiled选项创建的Regex,其factory成员变量会在Regex构造函数中赋值,对应的语句是factory=Compile(code,roptions);,类型是CompiledRegexRunnerFactory。

我们先来看看CompiledRegexRunnerFactory.CreateInstance()的实现。

CompiledRegexRunnerFactory.CreateInstance()

代码如下:

protectedinternaloverrideRegexRunnerCreateInstance(){\nCompiledRegexRunnerrunner=newCompiledRegexRunner();\n\nnewReflectionPermission(PermissionState.Unrestricted).Assert();\n//设置关键的动态函数,这三个函数是在`RegexLWCGCompiler`\n//类的`FactoryInstanceFromCode()`中生成的。\nrunner.SetDelegates(\n(NoParamDelegate)goMethod.CreateDelegate(typeof(NoParamDelegate)),\n(FindFirstCharDelegate)findFirstCharMethod.CreateDelegate(typeof(FindFirstCharDelegate)),\n(NoParamDelegate)initTrackCountMethod.CreateDelegate(typeof(NoParamDelegate))\n);\n\nreturnrunner;\n}\n

该函数返回的是CompiledRegexRunner类型的runner。在返回之前会先调用runner.SetDelegates为对应的关键函数(Go,FindFirstChar,InitTrackCount)赋值。参数中的goMethod,findFirstCharMethod,initTrackCountMethod是在哪里赋值的呢?在Regex.Compile()函数中赋值的。

Regex.Compile()

Regex.Compile()会直接转调RegexCompiler的静态函数Compile(),相关代码如下(有调整):

internalstaticRegexRunnerFactoryCompile(RegexCodecode,RegexOptionsoptions){\nRegexLWCGCompilerc=newRegexLWCGCompiler();\nreturnc.FactoryInstanceFromCode(code,options);\n}\n

该函数直接调用了RegexLWCGCompiler类的FactoryInstanceFromCode()成员函数。相关代码如下(有删减):

internalRegexRunnerFactoryFactoryInstanceFromCode(RegexCodecode,RegexOptionsoptions){\n\n//获取唯一标识符,也就是FindFirstChar后面的数字\nintregexnum=Interlocked.Increment(ref_regexCount);\nstringregexnumString=regexnum.ToString(CultureInfo.InvariantCulture);\n\n//生成动态函数Go\nDynamicMethodgoMethod=DefineDynamicMethod(&34;+regexnumString,null,typeof(CompiledRegexRunner));\nGenerateGo();\n\n//生成动态函数FindFirstChar\nDynamicMethodfirstCharMethod=DefineDynamicMethod(&34;+regexnumString,typeof(bool),typeof(CompiledRegexRunner));\nGenerateFindFirstChar();\n\n//生成动态函数InitTrackCount\nDynamicMethodtrackCountMethod=DefineDynamicMethod(&34;+regexnumString,null,typeof(CompiledRegexRunner));\nGenerateInitTrackCount();\n\nreturnnewCompiledRegexRunnerFactory(goMethod,firstCharMethod,trackCountMethod);\n}\n

该函数非常清晰易懂,但却是非常关键的一个函数,会生成三个动态函数(也就是通过PerfView采集到的FindFirstCharXXX,GoXXX,InitTrackCountXXX),最后会构造一个类型为CompiledRegexRunnerFactory的实例,并把生成的动态函数作为参数传递给CompiledRegexRunnerFactory的构造函数。

至此,已经找到生成动态函数的地方了。动态函数是什么时候被调用的呢?在runner.Scan()函数中被调用的。

RegexRunner.Scan()

关键代码如下(做了大量删减):

MatchScan(Regexregex,Stringtext,inttextbeg,inttextend,inttextstart,intprevlen,boolquick,TimeSpantimeout){\nfor(;;){\nif(FindFirstChar()){\nGo();\n\nif(runmatch._matchcount[0]>0)\nreturnTidyMatch(quick);\n}\n}\n}\n

可以看到,Scan()函数内部会调用FindFirstChar()和Go(),而且只有当FindFirstChar()返回true的时候,才会调用Go()。这两个函数是虚函数,具体的子类会重写。对于Compiled类型的正则表达式,对应的runner类型是CompiledRegexRunner。这三个关键的函数实现如下:

internalsealedclassCompiledRegexRunner:RegexRunner{\nNoParamDelegategoMethod;\nFindFirstCharDelegatefindFirstCharMethod;\nNoParamDelegateinitTrackCountMethod;\n\nprotectedoverridevoidGo(){\ngoMethod(this);\n}\n\nprotectedoverrideboolFindFirstChar(){\nreturnfindFirstCharMethod(this);\n}\n\nprotectedoverridevoidInitTrackCount(){\ninitTrackCountMethod(this);\n}\n}\n

现在可以回答疑问7疑问1引申出来的JIT问题了。

什么时候会生成动态方法?生成的动态方法是在哪里调用的?在指定了Compiled标志的Regex的构造函数内部会调用RegexCompiler.Compile()函数,Compile()函数又会调用RegexLWCGCompiler.FactoryInstanceFromCode(),FactoryInstanceFromCode()函数内部会分别调用GenerateFindFirstChar(),GenerateGo(),GenerateInitTrackCount()生成对应的动态方法。在执行MatchCollection.Count的时候,会调用MatchCollection.GetMatch()函数,GetMatch()函数会调用对应RegexRunner的Scan()函数。Scan()函数会调用RegexRunner.FindFirstChar(),而CompiledRegexRunner类型中的FindFirstChar()函数调用的是设置好的动态函数。

Compiled与非Compiled对比

1.构造函数

带Compiled选项的Regex

useCache传递的是false,表示不使用缓存。因为指定了RegexOptions.Compiled选项,Regex的构造函数内部会调用RegexCompiler.Compile()函数,Compile()函数又会调用RegexLWCGCompiler.FactoryInstanceFromCode(),FactoryInstanceFromCode()函数内部会分别调用GenerateFindFirstChar(),GenerateGo(),GenerateInitTrackCount()生成对应的动态方法,然后返回CompiledRegexRunnerFactory类型的实例。如下图:

compiled-regex-constructor

不带Compiled选项的Regex

构造函数与Compiled的基本一致,useCache传递的也是false,不使用缓存。因为UseOptionC()返回的是false,所以不会执行Compile()函数。所以factory成员变量是null。

这里就不贴图了。

2.matches.Count

带Compiled选项的Regex

MatchCollection-count-dynamic-FindFirstChar

MatchCollection.Count内部会调用GetMatch()函数,GetMatch()函数会调用对应RegexRunner的Scan()函数(这里的runner类型是CompiledRegexRunner)。Scan()内部会调用FindFirstChar()函数,而CompiledRegexRunner类型的FindFirstChar()函数内部调用的是设置好的动态方法。

不带Compiled选项的Regex

MatchCollection-count-none-dynamic-FindFirstChar

与带Compiled版本的调用栈基本一致,不一样的是这里runner的类型是RegexInterpreter,该类型的FindFirstChar()函数调用的代码不是动态生成的。

3.runner赋值

当runner是null的时候,需要根据情况获取对应的runner。

带Compiled选项的Regex

factory成员在Regex构造函数里通过Compile()赋过值,runner会通过下图1306行的factory.CreateInstance()赋值。

不带Compiled选项的Regex

factory成员没有被赋过值,因此是空的,runner会通过下图1308行的newRegexInterpreter()赋值。

runner

总结

不要在循环内部创建编译型的正则表达式(带Compiled选项),会频繁导致JIT的发生进而影响效率。Regex.IsMatch()也会创建Regex实例,但是最后一个参数bUseCache是true,表示使用缓存。Regex构造函数的最后一个参数bUseCache是true的时候才会更新缓存。正则表达式引擎内部会根据option+culture+pattern查找缓存。

参考资料

.NET源码

如果你还想了解更多这方面的信息,记得收藏关注本站。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平