<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          記一次 .NET 程序的性能優(yōu)化實(shí)戰(zhàn)(3)—— 深入 .NET 源碼

          共 11125字,需瀏覽 23分鐘

           ·

          2022-01-10 03:28

          前言

          前兩篇文章 part1part2 基本上理清了 IsSplitter() 運(yùn)行緩慢的原因 —— 在函數(shù)內(nèi)部使用了帶 Compile 選項(xiàng)的正則表達(dá)式。

          但是沒想到在 IsSplitter() 內(nèi)部使用不帶 Compiled 選項(xiàng)的正則表達(dá)式,整個(gè)程序運(yùn)行起來非常快,跟靜態(tài)函數(shù)版本的運(yùn)行速度不相上下。又有了如下疑問:

          1. 為什么使用不帶 Compiled 選項(xiàng)實(shí)例化的 Regex 速度會(huì)這么快?
          2. 為什么把 ?Regex 變量從局部改成全局變量后運(yùn)行速度有了極大提升?除了避免重復(fù)實(shí)例化,還有哪些提升?
          3. 為什么 PerfView 收集到的采樣數(shù)據(jù),大部分發(fā)生在 MatchCollections.Count 內(nèi)部,極少發(fā)生在 Regex 的構(gòu)造函數(shù)內(nèi)部?(使用帶 Compiled 選項(xiàng)的正則表達(dá)式的時(shí)候)
          4. Regex.IsMatch() 是如何使用緩存的?
          5. 直接實(shí)例化的 Regex 對(duì)象會(huì)使用正則表達(dá)式引擎內(nèi)部的緩存嗎?
          6. 正則表達(dá)式引擎內(nèi)部根據(jù)什么緩存的?
          7. 什么時(shí)候會(huì)生成動(dòng)態(tài)方法?生成的動(dòng)態(tài)方法是在哪里調(diào)用的?

          本文會(huì)繼續(xù)使用 Perfview 抓取一些關(guān)鍵數(shù)據(jù)進(jìn)行分析,有些疑問需要到 .NET 源碼中尋找答案。在查看代碼的過程中,發(fā)現(xiàn)有些邏輯單純看源碼不太容易理解,于是又調(diào)試跟蹤了 .NET 中正則表達(dá)式相關(guān)源碼。由于篇幅原因,本篇不會(huì)介紹如何下載 .NET 源碼,如何調(diào)試 .NET 源碼的方法。但是會(huì)單獨(dú)寫一篇簡(jiǎn)單的介紹文章 。

          解惑

          1. 為什么使用不帶 Compiled 選項(xiàng)實(shí)例化的 Regex 速度會(huì)這么快?

            還是使用 PerfView 采集性能數(shù)據(jù)并分析,如下圖:

            可以發(fā)現(xiàn), IsSplitter() 函數(shù)只在第一次被調(diào)用時(shí)發(fā)生了一次 JIT,后續(xù)調(diào)用耗時(shí)不到 0.1ms(圖中最后一次調(diào)用耗時(shí):4090.629-4090.597 = 0.032ms)。

            使用帶 Compiled 選項(xiàng)實(shí)例化的 RegexIsSplitter() 函數(shù),如下圖:

            view-filter-event-with-etwlogger

            每次調(diào)用大概要消耗 11ms5616.375 - 5604.637 = 11.738 ms)。

            至于為什么不帶 Compiled 選項(xiàng)的正則表達(dá)式在調(diào)用過程中沒有多余的 JIT,與疑問7一起到源碼中找答案。

          2. 為什么把 ?Regex 變量從局部改成全局變量后運(yùn)行速度有了極大提升?除了避免重復(fù)實(shí)例化,還有哪些提升?

            修改代碼,把局部變量改成全局變量,編譯。再次使用 PerfView 采集性能數(shù)據(jù)并分析,如下圖:

            可以發(fā)現(xiàn)與使用不帶 Compiled 選項(xiàng)的局部變量版本一樣,只發(fā)生了一次 JIT。所以把局部變量改成全局變量后,除了避免了重復(fù)實(shí)例化的開銷(很?。?,更重要的是避免了多余的 JIT 操作。

          3. 為什么 PerfView 收集到的采樣數(shù)據(jù),大部分發(fā)生在 MatchCollections.Count 內(nèi)部,極少發(fā)生在 Regex 的構(gòu)造函數(shù)內(nèi)部?(使用帶 Compiled 選項(xiàng)的正則表達(dá)式的時(shí)候)

            Regex 構(gòu)造函數(shù)只被 JIT 了一次,后面的調(diào)用都是在執(zhí)行原生代碼,執(zhí)行速度非???。而 MatchCollections.Count 每次執(zhí)行的時(shí)候都需要執(zhí)行 JIT(每次都需要 10ms 以 上),所以大部分?jǐn)?shù)據(jù)在 MatchCollections.Count 內(nèi)部,是非常合理的。

          4. Regex.IsMatch() 是如何使用緩存的?

            Regex.IsMatch() 有很多重載版本,最后都會(huì)調(diào)用下面的版本:

            static?bool?IsMatch(String?input,?String?pattern,?RegexOptions?options,?TimeSpan?matchTimeout)?{
            ??return?new?Regex(pattern,?options,?matchTimeout,?true).IsMatch(input);
            }

            該函數(shù)會(huì)在內(nèi)部構(gòu)造一個(gè)臨時(shí)的 Regex 對(duì)象,并且構(gòu)造函數(shù)的最后一個(gè)參數(shù) useCaChe 的值是 true,表示使用緩存。

          疑問5疑問6 的答案在 Regex 的構(gòu)造函數(shù)中,先看看 Regex 的構(gòu)造函數(shù)。

          Regex 構(gòu)造函數(shù)

          Regex 有很多個(gè)構(gòu)造函數(shù),列舉如下:

          public?Regex(String?pattern)
          ??:?this(pattern,?RegexOptions.None,?DefaultMatchTimeout,?false)
          ?{}
          ??
          public?Regex(String?pattern,?RegexOptions?options)
          ??:?this(pattern,?options,?DefaultMatchTimeout,?false)
          ?{}
          ????????
          Regex(String?pattern,?RegexOptions?options,?TimeSpan?matchTimeout)
          ??:?this(pattern,?options,?matchTimeout,?false)?{}

          注意: 以上構(gòu)造函數(shù)的最后一個(gè)參數(shù)都是 false,表示不使用緩存。

          這些構(gòu)造函數(shù)最后都會(huì)調(diào)用下面的私有構(gòu)造函數(shù)(代碼有所精簡(jiǎn)調(diào)整):

          private?Regex(String?pattern,?RegexOptions?options,?TimeSpan?matchTimeout,?bool?useCache)
          {
          ??string?cultureKey?=?null;
          ??if?((options?&?RegexOptions.CultureInvariant)?!=?0)
          ??????cultureKey?=?CultureInfo.InvariantCulture.ToString();?//?"English?(United?States)"
          ??else
          ??????cultureKey?=?CultureInfo.CurrentCulture.ToString();
          ??
          ??//?構(gòu)造緩存用到的?key,包含?options,culture?和?pattern
          ??String?key?=?((int)?options).ToString(NumberFormatInfo.InvariantInfo)?+?":"?+?cultureKey?+?":"?+?pattern;
          ??CachedCodeEntry?cached?=?LookupCachedAndUpdate(key);

          ??this.pattern?=?pattern;
          ??this.roptions?=?options;

          ??if?(cached?==?null)?{
          ??????//?如果沒找到緩存就生成類型為?RegexCodes?的?code,包含了字節(jié)碼等信息
          ??????RegexTree?tree?=?RegexParser.Parse(pattern,?roptions);
          ??????code?=?RegexWriter.Write(tree);
          ??????//?如果指定了?useCache?參數(shù)就緩存起來,下次就能在緩存中找到了
          ??????if?(useCache)
          ??????????cached?=?CacheCode(key);
          ??}?else?{
          ??????//?如果找到了緩存就使用緩存中的信息
          ??????code???????=?cached._code;
          ??????factory????=?cached._factory;
          ??????runnerref??=?cached._runnerref;
          ??}

          ??//?如果指定了?Compiled?選項(xiàng),并且?factory?是空(沒使用緩存,或者緩存中的?_factory?是空)
          ??if?(UseOptionC()?&&?factory?==?null)?{
          ??????//?根據(jù)?code?和?roptions?生成?factory
          ??????factory?=?Compile(code,?roptions);
          ??
          ??????//?需要緩存就緩存起來
          ??????if?(useCache?&&?cached?!=?null)
          ??????????cached.AddCompiled(factory);
          ??}
          }

          注意:bool useCache 標(biāo)記的構(gòu)造函數(shù)是私有的,也就是說不能直接使用此構(gòu)造函數(shù)實(shí)例化 Regex。

          首先會(huì)根據(jù) option + culture + pattern 到緩存中查找。如果沒找到緩存就生成類型為 RegexCodescode(包含了字節(jié)碼等信息),如果找到了緩存就使用緩存中的信息。如果指定了 Compiled 選項(xiàng)(UseOptionC() 會(huì)返回 true),并且 factory 是空(沒使用緩存或者緩存中的 _factory 是空),就會(huì)執(zhí)行 Compile() 函數(shù),并把返回值保存到 factory 成員中。

          至此,可以回答第 5 6 兩個(gè)疑問了。

          1. 直接實(shí)例化的 Regex 對(duì)象會(huì)使用正則表達(dá)式引擎內(nèi)部的緩存嗎?

            會(huì)優(yōu)先根據(jù) option + culture + pattern 到緩存中查找,但是否更新緩存是由最后一個(gè)參數(shù) useCache 決定的,與是否指定 Compiled 選項(xiàng)無關(guān)。

          2. 正則表達(dá)式引擎內(nèi)部根據(jù)什么緩存的?

            根據(jù) option + culture + pattern 緩存。

          疑問7 與由 疑問1 引申出來的 JIT 問題是一個(gè)問題。之所以會(huì) JIT,是因?yàn)橛行枰?JIT 的代碼,如果不斷有新的動(dòng)態(tài)方法產(chǎn)生出來并執(zhí)行,那么就需要不斷地 JIT。由于此問題涉及到的代碼量比較大,邏輯比較復(fù)雜,需要深入 .NET 源碼進(jìn)行查看。為了更好的理解整個(gè)過程,我簡(jiǎn)單梳理了 IsSpitter() 函數(shù)中涉及到的關(guān)鍵類以及類之間的關(guān)系,整理成下圖,供參考。

          流程 & 類關(guān)系梳理

          看完上圖后,可以繼續(xù)看剩下的 JIT 問題了。因?yàn)榇蠖鄶?shù) JIT 都出現(xiàn)在 MatchCollection.Count 中,可以由此切入。

          MatchCollection.Count

          實(shí)現(xiàn)代碼如下:

          public?int?Count?{
          ??get?{
          ????if?(_done)
          ??????return?_matches.Count;
          ????GetMatch(infinite);
          ????return?_matches.Count;
          ??}
          }

          Count 會(huì)調(diào)用 GetMatch() 函數(shù),而 GetMatch() 函數(shù)會(huì)不斷調(diào)用 _regex.Run() 函數(shù)。

          _regex 是哪來的呢?在構(gòu)造 MatchCollection 實(shí)例時(shí)傳過來的。

          MatchCollection 是由 Regex.Matches() 實(shí)例化的,代碼如下(去掉了判空邏輯):

          public?MatchCollection?Matches(String?input,?int?startat)?{
          ??return?new?MatchCollection(this,?input,?0,?input.Length,?startat);
          }

          該函數(shù)會(huì)實(shí)例化一個(gè) MatchCollection 對(duì)象,并把當(dāng)前 Regex 實(shí)例作為第一個(gè)參數(shù)傳給 MatchCollection 的構(gòu)造函數(shù)。該參數(shù)會(huì)被保存到 MatchCollection 實(shí)例的 _regex 成員中。

          接下來繼續(xù)查看 Regex.Run 函數(shù)的實(shí)現(xiàn)。

          Regex.Run()

          具體實(shí)現(xiàn)代碼如下(代碼有精簡(jiǎn)):

          internal?Match?Run(bool?quick,?int?prevlen,?String?input,?int?beginning,?int?length,?int?startat)?{
          ????Match?match;
          ????//?使用緩存的時(shí)候,可能從緩存中拿到一個(gè)有效的 runner,其它情況下都是 null。
          ????RegexRunner?runner?=?(RegexRunner)runnerref.Get();

          ????//?不使用緩存的時(shí)候?runner是?null
          ????if?(runner?==?null)?{
          ????????//?如果 factory 不為空就通過 factory 創(chuàng)建一個(gè) runner。
          ????????//?使用了?Compiled?標(biāo)志創(chuàng)建的?Regex?實(shí)例的?factory?不為空
          ????????if?(factory?!=?null)
          ????????????runner?=?factory.CreateInstance();
          ????????else
          ????????????runner?=?new?RegexInterpreter(code,?UseOptionInvariant()???CultureInfo.InvariantCulture?:?CultureInfo.CurrentCulture);
          ????}

          ????try?{
          ????????//?調(diào)用 RegexRunner.Scan 掃描匹配項(xiàng)。
          ????????match?=?runner.Scan(this,?input,?beginning,?beginning?+?length,?startat,?prevlen,?quick,?internalMatchTimeout);
          ????}?finally?{
          ????????runnerref.Release(runner);
          ????}

          ????return?match;
          }

          邏輯還是非常清晰的,先找到或者創(chuàng)建(通過 factory.CreateInstance() 或者直接 new)一個(gè)類型為 RegexRunner 實(shí)例 runner,然后調(diào)用 runner->Scan() 進(jìn)行匹配。

          對(duì)于使用 Compiled 選項(xiàng)創(chuàng)建的 Regex,其 factory 成員變量會(huì)在 Regex 構(gòu)造函數(shù)中賦值,對(duì)應(yīng)的語(yǔ)句是 factory = Compile(code, roptions); ,類型是 CompiledRegexRunnerFactory。

          我們先來看看 CompiledRegexRunnerFactory.CreateInstance() 的實(shí)現(xiàn)。

          CompiledRegexRunnerFactory.CreateInstance()

          代碼如下:

          protected?internal?override?RegexRunner?CreateInstance()?{
          ??CompiledRegexRunner?runner?=?new?CompiledRegexRunner();

          ??new?ReflectionPermission(PermissionState.Unrestricted).Assert();
          ??//?設(shè)置關(guān)鍵的動(dòng)態(tài)函數(shù),這三個(gè)函數(shù)是在?`RegexLWCGCompiler`
          ??//?類的?`FactoryInstanceFromCode()`?中生成的。
          ??runner.SetDelegates(
          ????(NoParamDelegate)?goMethod.CreateDelegate(typeof(NoParamDelegate)),
          ????(FindFirstCharDelegate)?findFirstCharMethod.CreateDelegate(typeof(FindFirstCharDelegate)),
          ????(NoParamDelegate)?initTrackCountMethod.CreateDelegate(typeof(NoParamDelegate))
          ??);

          ??return?runner;
          }

          該函數(shù)返回的是 CompiledRegexRunner 類型的 runner。在返回之前會(huì)先調(diào)用 runner.SetDelegates 為對(duì)應(yīng)的關(guān)鍵函數(shù)(Go, FindFirstChar, InitTrackCount)賦值。參數(shù)中的 goMethod, findFirstCharMethod, initTrackCountMethod 是在哪里賦值的呢?在 Regex.Compile() 函數(shù)中賦值的。

          Regex.Compile()

          Regex.Compile() 會(huì)直接轉(zhuǎn)調(diào) RegexCompiler 的靜態(tài)函數(shù) Compile(),相關(guān)代碼如下(有調(diào)整):

          internal?static?RegexRunnerFactory?Compile(RegexCode?code,?RegexOptions?options)?{
          ??RegexLWCGCompiler?c?=?new?RegexLWCGCompiler();
          ??return?c.FactoryInstanceFromCode(code,?options);
          }

          該函數(shù)直接調(diào)用了 RegexLWCGCompiler 類的 FactoryInstanceFromCode() 成員函數(shù)。相關(guān)代碼如下(有刪減):

          internal?RegexRunnerFactory?FactoryInstanceFromCode(RegexCode?code,?RegexOptions?options)?{

          ??//?獲取唯一標(biāo)識(shí)符,也就是FindFirstChar后面的數(shù)字
          ??int?regexnum?=?Interlocked.Increment(ref?_regexCount);
          ??string?regexnumString?=?regexnum.ToString(CultureInfo.InvariantCulture);

          ??//?生成動(dòng)態(tài)函數(shù)Go
          ??DynamicMethod?goMethod?=?DefineDynamicMethod("Go"?+?regexnumString,?null,?typeof(CompiledRegexRunner));
          ??GenerateGo();

          ??//?生成動(dòng)態(tài)函數(shù)FindFirstChar
          ??DynamicMethod?firstCharMethod?=?DefineDynamicMethod("FindFirstChar"?+?regexnumString,?typeof(bool),?typeof(CompiledRegexRunner));
          ??GenerateFindFirstChar();
          ??
          ??//?生成動(dòng)態(tài)函數(shù)InitTrackCount??
          ??DynamicMethod?trackCountMethod?=?DefineDynamicMethod("InitTrackCount"?+?regexnumString,?null,?typeof(CompiledRegexRunner));
          ??GenerateInitTrackCount();

          ??return?new?CompiledRegexRunnerFactory(goMethod,?firstCharMethod,?trackCountMethod);
          }

          該函數(shù)非常清晰易懂,但卻是非常關(guān)鍵的一個(gè)函數(shù),會(huì)生成三個(gè)動(dòng)態(tài)函數(shù)(也就是通過 PerfView 采集到的 FindFirstCharXXX,GoXXX,InitTrackCountXXX),最后會(huì)構(gòu)造一個(gè)類型為 CompiledRegexRunnerFactory 的實(shí)例,并把生成的動(dòng)態(tài)函數(shù)作為參數(shù)傳遞給 CompiledRegexRunnerFactory 的構(gòu)造函數(shù)。

          至此,已經(jīng)找到生成動(dòng)態(tài)函數(shù)的地方了。動(dòng)態(tài)函數(shù)是什么時(shí)候被調(diào)用的呢?在 runner.Scan() 函數(shù)中被調(diào)用的。

          RegexRunner.Scan()

          關(guān)鍵代碼如下(做了大量刪減):

          Match?Scan(Regex?regex,?String?text,?int?textbeg,?int?textend,?int?textstart,?int?prevlen,?bool?quick,?TimeSpan?timeout)?{
          ??for?(;?;?)?{
          ????if?(FindFirstChar())?{
          ??????Go();

          ??????if?(runmatch._matchcount?[0]?>?0)
          ????????return?TidyMatch(quick);
          ????}
          ??}
          }???????????????????????

          可以看到,Scan() 函數(shù)內(nèi)部會(huì)調(diào)用 FindFirstChar()Go(),而且只有當(dāng) FindFirstChar() 返回 true 的時(shí)候,才會(huì)調(diào)用 Go()。這兩個(gè)函數(shù)是虛函數(shù),具體的子類會(huì)重寫。對(duì)于 Compiled 類型的正則表達(dá)式,對(duì)應(yīng)的 runner 類型是 CompiledRegexRunner。這三個(gè)關(guān)鍵的函數(shù)實(shí)現(xiàn)如下:

          internal?sealed?class?CompiledRegexRunner?:?RegexRunner?{
          ??NoParamDelegate?goMethod;
          ??FindFirstCharDelegate?findFirstCharMethod;
          ??NoParamDelegate?initTrackCountMethod;
          ????????
          ??protected?override?void?Go()?{
          ????goMethod(this);
          ??}

          ??protected?override?bool?FindFirstChar()?{
          ????return?findFirstCharMethod(this);
          ??}

          ??protected?override?void?InitTrackCount()?{
          ????initTrackCountMethod(this);
          ??}
          }

          現(xiàn)在可以回答疑問7疑問1 引申出來的 JIT 問題了。

          1. 什么時(shí)候會(huì)生成動(dòng)態(tài)方法?生成的動(dòng)態(tài)方法是在哪里調(diào)用的?

            在指定了 Compiled 標(biāo)志的 Regex 的構(gòu)造函數(shù)內(nèi)部會(huì)調(diào)用 RegexCompiler.Compile() 函數(shù),Compile() 函數(shù)又會(huì)調(diào)用 RegexLWCGCompiler.FactoryInstanceFromCode(),FactoryInstanceFromCode() 函數(shù)內(nèi)部會(huì)分別調(diào)用 GenerateFindFirstChar(), GenerateGo(), GenerateInitTrackCount() 生成對(duì)應(yīng)的動(dòng)態(tài)方法。

            在執(zhí)行 MatchCollection.Count 的時(shí)候,會(huì)調(diào)用 MatchCollection.GetMatch() 函數(shù),GetMatch() 函數(shù)會(huì)調(diào)用對(duì)應(yīng) RegexRunnerScan() 函數(shù)。Scan() 函數(shù)會(huì)調(diào)用 RegexRunner.FindFirstChar(),而 CompiledRegexRunner 類型中的 FindFirstChar() 函數(shù)調(diào)用的是設(shè)置好的動(dòng)態(tài)函數(shù)。

          Compiled 與 非 Compiled 對(duì)比

          1. 構(gòu)造函數(shù)

          Compiled 選項(xiàng)的 Regex?

          useCache 傳遞的是 false,表示不使用緩存。因?yàn)橹付?RegexOptions.Compiled 選項(xiàng), Regex 的構(gòu)造函數(shù)內(nèi)部會(huì)調(diào)用 RegexCompiler.Compile() 函數(shù),Compile() 函數(shù)又會(huì)調(diào)用 RegexLWCGCompiler.FactoryInstanceFromCode(),FactoryInstanceFromCode() 函數(shù)內(nèi)部會(huì)分別調(diào)用 GenerateFindFirstChar(), GenerateGo(), GenerateInitTrackCount() 生成對(duì)應(yīng)的動(dòng)態(tài)方法,然后返回 CompiledRegexRunnerFactory 類型的實(shí)例。如下圖:

          compiled-regex-constructor

          不帶 Compiled 選項(xiàng)的 Regex?

          構(gòu)造函數(shù)與 Compiled 的基本一致,useCache 傳遞的也是 false,不使用緩存。因?yàn)?UseOptionC() 返回的是 false,所以不會(huì)執(zhí)行 Compile() 函數(shù)。所以 factory 成員變量是 null。

          這里就不貼圖了。

          2. matches.Count

          Compiled 選項(xiàng)的 Regex?

          MatchCollection-count-dynamic-FindFirstChar

          MatchCollection.Count 內(nèi)部會(huì)調(diào)用 GetMatch() 函數(shù),GetMatch() 函數(shù)會(huì)調(diào)用對(duì)應(yīng) RegexRunnerScan() 函數(shù)(這里的 runner 類型是 CompiledRegexRunner)。Scan() 內(nèi)部會(huì)調(diào)用 FindFirstChar() 函數(shù),而 CompiledRegexRunner 類型的 FindFirstChar() 函數(shù)內(nèi)部調(diào)用的是設(shè)置好的動(dòng)態(tài)方法。

          不帶 Compiled 選項(xiàng)的 Regex?

          MatchCollection-count-none-dynamic-FindFirstChar

          與帶 Compiled 版本的調(diào)用?;疽恢拢灰粯拥氖沁@里 runner 的類型是 RegexInterpreter,該類型的 FindFirstChar() 函數(shù)調(diào)用的代碼不是動(dòng)態(tài)生成的。

          3. runner 賦值

          當(dāng) runnernull 的時(shí)候,需要根據(jù)情況獲取對(duì)應(yīng)的 runner。

          Compiled 選項(xiàng)的 Regex?

          factory 成員在 Regex 構(gòu)造函數(shù)里通過 Compile() 賦過值,runner 會(huì)通過下圖 1306 行的 factory.CreateInstance() 賦值。

          不帶 Compiled 選項(xiàng)的 Regex?

          factory 成員沒有被賦過值,因此是空的,runner 會(huì)通過下圖 1308 行的 new RegexInterpreter() 賦值。

          runner

          總結(jié)

          • 不要在循環(huán)內(nèi)部創(chuàng)建編譯型的正則表達(dá)式(帶 Compiled 選項(xiàng)),會(huì)頻繁導(dǎo)致 JIT 的發(fā)生進(jìn)而影響效率。
          • Regex.IsMatch() 也會(huì)創(chuàng)建 Regex 實(shí)例,但是最后一個(gè)參數(shù) bUseCachetrue,表示使用緩存。
          • Regex 構(gòu)造函數(shù)的最后一個(gè)參數(shù) bUseCachetrue 的時(shí)候才會(huì)更新緩存。
          • 正則表達(dá)式引擎內(nèi)部會(huì)根據(jù) option + culture + pattern 查找緩存。

          參考資料

          .NET源碼? ??https://referencesource.microsoft.com/


          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼网站免费在线观看 | 亚洲综合在线人妻 | 逼逼逼逼五月情 | 亚洲色播免费视频 | 18禁黄网站禁片免费看 |