日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

.NET 5 中的正则引擎性能改进(翻译)

發布時間:2023/12/4 asp.net 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET 5 中的正则引擎性能改进(翻译) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

System.Text.RegularExpressions?命名空間已經在 .NET 中使用了多年,一直追溯到 .NET Framework 1.1。它在 .NET 實施本身的數百個位置中使用,并且直接被成千上萬個應用程序使用。在所有這些方面,它也是 CPU 消耗的重要來源。

但是,從性能角度來看,正則表達式在這幾年間并沒有獲得太多關注。在 2006 年的 .NET Framework 2.0 中更改了其緩存策略。.NET Core 2.0 在?RegexOptions.Compiled?之后看到了這個實現的到來(在 .NET Core 1.x 中,RegexOptions.Compiled?選項是一個 nop)。.NET Core 3.0 受益于 Regex 內部的一些內部更新,以在某些情況下利用?Span<T>?提高內存利用率。在此過程中,一些非常受歡迎的社區貢獻改進了目標區域,例如?dotnet/corefx#32899,它減少了使用表達式?RegexOptions.Compiled | RegexOptions.IgnoreCase?時對CultureInfo.CurrentCulture?的訪問。但除此之外,實施很大程度上還是在15年前。

對于 .NET 5(本周發布了?Preview 2),我們已對 Regex 引擎進行了一些重大改進。在我們嘗試過的許多表達式中,這些更改通常會使吞吐量提高3到6倍,在某些情況下甚至會提高更多。在本文中,我將逐步介紹 .NET 5 中?System.Text.RegularExpressions?進行的許多更改。這些更改對我們自己的使用產生了可衡量的影響,我們希望這些改進將帶來可衡量的勝利在您的庫和應用中。

Regex內部知識

要了解所做的某些更改,了解一些Regex內部知識很有幫助。

Regex構造函數完成所有工作,以采用正則表達式模式并準備對其進行匹配輸入:

  • RegexParser。該模式被送入內部RegexParser類型,該類型理解正則表達式語法并將其解析為節點樹。例如,表達式a|bcd被轉換為具有兩個子節點的“替代”?RegexNode,一個子節點表示單個字符a,另一個子節點表示“多個”?bcd。解析器還對樹進行優化,將一棵樹轉換為另一個等效樹,以提供更有效的表示和/或可以更高效地執行該樹。

  • RegexWriter。節點樹不是執行匹配的理想表示,因此解析器的輸出將饋送到內部RegexWriter類,該類會寫出一系列緊湊的操作碼,以表示執行匹配的指令。這種類型的名稱是“ writer”,因為它“寫”出了操作碼。其他引擎通常將其稱為“編譯”,但是 .NET 引擎使用不同的術語,因為它保留了“編譯”術語,用于 MSIL 的可選編譯。

  • RegexCompiler(可選)。如果未指定RegexOptions.Compiled選項,則內部RegexInterpreter類稍后在匹配時使用RegexWriter輸出的操作碼來解釋/執行執行匹配的指令,并且在Regex構造過程中不需要任何操作。但是,如果指定了RegexOptions.Compiled,則構造函數將獲取先前輸出的資產,并將其提供給內部RegexCompiler類。然后,RegexCompiler使用反射發射生成MSIL,該MSIL表示解釋程序將要執行的工作,但專門針對此特定表達式。例如,當與模式中的字符“ c”匹配時,解釋器將需要從變量中加載比較值,而編譯器會將“ c”硬編碼為生成的IL中的常量。

一旦構造了正則表達式,就可以通過IsMatch,Match,Matches,Replace和Split等實例方法將其用于匹配(Match返回Match對象,該對象公開了NextMatch方法,該方法可以迭代匹配并延遲計算) 。這些操作最終以“掃描”循環(某些其他引擎將其稱為“傳輸”循環)結束,該循環本質上執行以下操作:

Copywhile (FindFirstChar()) {Go();if (_match != null)return _match;_pos++; } return null;

_pos是我們在輸入中所處的當前位置。virtual FindFirstChar從_pos開始,并在輸入文本中查找正則表達式可能匹配的第一位;這并不是執行完整引擎,而是盡可能高效地進行搜索,以找到值得運行完整引擎的位置。?FindFirstChar可以最大程度地減少誤報,并且找到有效位置的速度越快,表達式的處理速度就越快。如果找不到合適的起點,則可能沒有任何匹配,因此我們完成了。如果找到了一個好的起點,它將更新_pos,然后通過調用virtual Go來在找到的位置執行引擎。如果Go找不到匹配項,我們會碰到當前位置并重新開始,但是如果Go找到匹配項,它將存儲匹配信息并返回該數據。顯然,執行Go的速度也越快越好。

所有這些邏輯都在公共RegexRunner基類中。?RegexInterpreter派生自RegexRunner,并用解釋正則表達式的實現覆蓋FindFirstChar和Go,這由RegexWriter生成的操作碼表示。?RegexCompiler使用DynamicMethods生成兩種方法,一種用于FindFirstChar,另一種用于Go。委托是從這些創建的、從RegexRunner派生的另一種類型調用。

.NET 5的改進

在本文的其余部分中,我們將逐步介紹針對 .NET 5 中的 Regex 進行的各種優化。這不是詳盡的清單,但它突出了一些最具影響力的更改。

CharInClass

正則表達式支持“字符類”,它們定義了輸入字符應該或不應該匹配的字符集,以便將該位置視為匹配字符。字符類用方括號表示。這里有些例子:

  • [abc]?匹配“ a”,“ b”或“ c”。

  • [^\n]?匹配換行符以外的任何字符。(除非指定了?RegexOptions.Singleline,否則這是您在表達式中使用的確切字符類。)

  • [a-cx-z]?匹配“ a”,“ b”,“ c”,“ x”,“ y”或“ z”。

  • [\d\s\p{IsGreek}]?匹配任何Unicode數字,空格或希臘字符。(與大多數其他正則表達式引擎相比,這是一個有趣的區別。例如,在其他引擎中,默認情況下,\d通常映射到[0-9],您可以選擇加入,而不是映射到所有Unicode數字,即[\p{Nd}],而在.NET中,您默認情況下會使用后者,并使用?RegexOptions.ECMAScript?選擇退出。)

當將包含字符類的模式傳遞給Regex構造函數時,RegexParser的工作之一就是將該字符類轉換為可以在運行時更輕松地查詢的字符。解析器使用內部RegexCharClass類型來解析字符類,并從本質上提取三件事(還有更多東西,但這對于本次討論就足夠了):

  • 模式是否被否定

  • 匹配字符范圍的排序集

  • 匹配字符的Unicode類別的排序集

這是所有實現的詳細信息,但是該信息然后保留在字符串中,該字符串可以傳遞給受保護的?RegexRunner.CharInClass?方法,以確定字符類中是否包含給定的Char。

在.NET 5之前,每一次需要將一個字符與一個字符類進行匹配時,它將調用該CharInClass方法。然后,CharInClass對范圍進行二進制搜索,以確定指定字符是否存儲在一個字符中;如果不存儲,則獲取目標字符的Unicode類別,并對Unicode類別進行線性搜索,以查看是否匹配。因此,對于^\d*$之類的表達式(斷言它在行的開頭,然后匹配任意數量的Unicode數字,然后斷言在行的末尾),假設輸入了1000位數字,這加起來將對CharInClass進行1000次調用。

在 .NET 5 中,我們現在更加聰明地做到了這一點,尤其是在使用RegexOptions.Compiled時,通常,只要開發人員非常關心Regex的吞吐量,就可以使用它。一種解決方案是,對于每個字符類,維護一個查找表,該表將輸入字符映射到有關該字符是否在類中的是/否決定。雖然我們可以這樣做,但是System.Char是一個16位的值,這意味著每個字符一個位,我們需要為每個字符類使用8K查找表,并且這還要累加起來。取而代之的是,我們首先嘗試使用平臺中的現有功能或通過簡單的數學運算來快速進行匹配,以處理一些常見情況。例如,對于\d,我們現在不生成對RegexRunner.CharInClass(ch, charClassString)?的調用,而是僅生成對?char.IsDigit(ch)的調用。?IsDigit已經使用查找表進行了優化,可以內聯,并且性能非常好。類似地,對于\s,我們現在生成對char.IsWhitespace(ch)的調用。對于僅包含幾個字符的簡單字符類,我們將生成直接比較,例如對于[az],我們將生成等價于(ch =='a') | (ch =='z')。對于僅包含單個范圍的簡單字符類,我們將通過一次減法和比較來生成檢查,例如[a-z]導致(uint)ch-'a'<= 26,而?[^ 0-9]?導致?!((uint)c-'0'<= 10)。我們還將特殊情況下的其他常見規范;例如,如果整個字符類都是一個Unicode類別,我們將僅生成對char.GetUnicodeInfo(也具有快速查找表)的調用,然后進行比較,例如[\p{Lu}]變為char.GetUnicodeInfo(c)== UnicodeCategory.UppercaseLetter。

當然,盡管涵蓋了許多常見情況,但當然并不能涵蓋所有情況。而且,因為我們不想為每個字符類生成8K查找表,并不意味著我們根本無法生成查找表。相反,如果我們沒有遇到這些常見情況之一,那么我們確實會生成一個查找表,但僅針對ASCII,它只需要16個字節(128位),并且考慮到正則表達式中的典型輸入,這往往是一個很好的折衷方案基于方案。由于我們使用DynamicMethod生成方法,因此我們不容易將附加數據存儲在程序集的靜態數據部分中,但是我們可以做的就是利用常量字符串作為數據存儲;MSIL具有用于加載常量字符串的操作碼,并且反射發射對生成此類指令具有良好的支持。因此,對于每個查找表,我們只需創建所需的8個字符的字符串,用不透明的位圖數據填充它,然后在IL中用ldstr吐出。然后我們可以像對待其他任何位圖一樣對待它,例如為了確定給定的字符是否匹配,我們生成以下內容:

Copybool result = ch < 128 ? (lookup[c >> 4] & (1 << (c & 0xF))) != 0 : NonAsciiFallback;

換句話說,我們使用字符的高三位選擇查找表字符串中的第0至第7個字符,然后使用低四位作為該位置16位值的索引;如果是1,則表示匹配,如果不是,則表示沒有匹配。對于大于等于128的字符,我們需要一個回退,根據對字符類進行的一些分析,回退可能是各種各樣的事情。最糟糕的情況是,回退只是對RegexRunner.CharInClass的調用,否則我們會做得更好。例如,很常見的是,我們可以從輸入模式中得知所有可能的匹配項均小于<128,在這種情況下,我們根本不需要回退,例如 對于字符類[0-9a-fA-F](又稱十六進制),我們將生成以下內容:

Copybool result = ch < 128 && (lookup[c >> 4] & (1 << (c & 0xF))) != 0;

相反,我們可以確定127以上的每個字符都將去匹配。例如,字符類[^aeiou](除ASCII小寫元音外的所有字符)將產生與以下代碼等效的代碼:

Copybool result = ch >= 128 || (lookup[c >> 4] & (1 << (c & 0xF))) != 0;

等等。

以上都是針對RegexOptions.Compiled,但解釋表達式并不會被冷落。對于解釋表達式,我們當前會生成一個類似的查找表,但是我們這樣做是很懶惰的,第一次看到給定輸入字符時會填充該表,然后針對該字符類針對該字符的所有將來評估存儲該答案。(我們可能會重新研究如何執行此操作,但這是從 .NET 5 Preview 2 開始存在的方式。)

這樣做的最終結果可能是頻繁評估字符類的表達式的吞吐量顯著提高。例如,這是一個微基準測試,可將ASCII字母和數字與具有62個此類值的輸入進行匹配:

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private Regex _regex = new Regex("[a-zA-Z0-9]*", RegexOptions.Compiled);[Benchmark] public bool IsMatch() => _regex.IsMatch("abcdefghijklmnopqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); }

這是我的項目文件:

Copy<project Sdk="Microsoft.NET.Sdk"><propertygroup><langversion>preview</langversion><outputtype>Exe</outputtype><targetframeworks>netcoreapp5.0;netcoreapp3.1</targetframeworks></propertygroup><itemgroup><packagereference Include="benchmarkdotnet" Version="0.12.0.1229"></packagereference></itemgroup> </project>

在我的計算機上,我有兩個目錄,一個包含.NET Core 3.1,一個包含.NET 5的內部版本(此處標記為master,因為它是dotnet/runtime的master分支的內部版本)。當我執行以上操作針對兩個版本運行基準測試:

Copydotnet run -c Release -f netcoreapp3.1 --filter ** --corerun d:\coreclrtest\netcore31\corerun.exe d:\coreclrtest\master\corerun.exe

我得到了以下結果:

MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe102.3 ns1.33 ns1.24 ns0.17
IsMatch\netcore31\corerun.exe585.7 ns2.80 ns2.49 ns1.00

開發人員可能會寫的代碼生成器

如前所述,當RegexOptions.Compiled與Regex一起使用時,我們使用反射發射為其生成兩種方法,一種實現FindFirstChar,另一種實現Go。為了支持回溯,Go最終包含了很多通常不需要的代碼。生成代碼的方式通常包括不必要的字段讀取和寫入,導致檢查JIT無法消除的邊界等。在 .NET 5 中,我們改進了為許多表達式生成的代碼。

考慮表達式@"a\sb",它匹配一個'a',任何Unicode空格和一個'b'。以前,反編譯為Go發出的IL看起來像這樣:

Copypublic override void Go() {string runtext = base.runtext;int runtextstart = base.runtextstart;int runtextbeg = base.runtextbeg;int runtextend = base.runtextend;int num = runtextpos;int[] runtrack = base.runtrack;int runtrackpos = base.runtrackpos;int[] runstack = base.runstack;int runstackpos = base.runstackpos;CheckTimeout();runtrack[--runtrackpos] = num;runtrack[--runtrackpos] = 0;CheckTimeout();runstack[--runstackpos] = num;runtrack[--runtrackpos] = 1;CheckTimeout();if (num < runtextend && runtext[num++] == 'a'){CheckTimeout();if (num < runtextend && RegexRunner.CharInClass(runtext[num++], "\0\0\u0001d")){CheckTimeout();if (num < runtextend && runtext[num++] == 'b'){CheckTimeout();int num2 = runstack[runstackpos++];Capture(0, num2, num);runtrack[--runtrackpos] = num2;runtrack[--runtrackpos] = 2;goto IL_0131;}}}while (true){base.runtrackpos = runtrackpos;base.runstackpos = runstackpos;EnsureStorage();runtrackpos = base.runtrackpos;runstackpos = base.runstackpos;runtrack = base.runtrack;runstack = base.runstack;switch (runtrack[runtrackpos++]){case 1:CheckTimeout();runstackpos++;continue;case 2:CheckTimeout();runstack[--runstackpos] = runtrack[runtrackpos++];Uncapture();continue;}break;}CheckTimeout();num = runtrack[runtrackpos++];goto IL_0131;IL_0131:CheckTimeout();runtextpos = num; }

那里有很多東西,需要斜視和搜索才能將實現的核心看作方法的中間幾行。現在在.NET 5中,相同的表達式導致生成以下代碼:

Copyprotected override void Go() {string runtext = base.runtext;int runtextend = base.runtextend;int runtextpos;int start = runtextpos = base.runtextpos;ReadOnlySpan<char> readOnlySpan = runtext.AsSpan(runtextpos, runtextend - runtextpos);if (0u < (uint)readOnlySpan.Length && readOnlySpan[0] == 'a' &&1u < (uint)readOnlySpan.Length && char.IsWhiteSpace(readOnlySpan[1]) &&2u < (uint)readOnlySpan.Length && readOnlySpan[2] == 'b'){Capture(0, start, base.runtextpos = runtextpos + 3);} }

如果您像我一樣,則可以注視著眼睛看第一個版本,但是如果您看到第二個版本,則可以真正閱讀并了解它的功能。除了易于理解和易于調試之外,它還減少了執行的代碼,消除了邊界檢查,減少了對字段和數組的讀寫等方面的工作。最終的結果是它的執行速度也快得多。(這里還有進一步改進的可能性,例如刪除兩個長度檢查,可能會重新排序一些檢查,但總的來說,它比以前有了很大的改進。)

向量化的基于?Span?的搜索

正則表達式都是關于搜索內容的。結果,我們經常發現自己正在運行循環以尋找各種事物。例如,考慮表達式?hello.*world。以前,如果要反編譯我們在Go方法中生成的用于匹配.*的代碼,則該代碼類似于以下內容:

Copywhile (--num3 > 0) {if (runtext[num++] == '\n'){num--;break;} }

換句話說,我們將手動遍歷輸入文本字符串,逐個字符地查找?\n(請記住,默認情況下,.表示“?\n以外的任何內容”,因此.*表示“匹配所有內容,直到找到\n” )。但是,.NET早已擁有完全執行此類搜索的方法,例如IndexOf,并且從最新版本開始,IndexOf是矢量化的,因此它可以同時比較多個字符,而不僅僅是單獨查看每個字符。現在,在.NET 5中,我們不再像上面那樣生成代碼,而是得到如下代碼:

Copynum2 = runtext.AsSpan(runtextpos, num).IndexOf('\n');

使用IndexOf而不是生成我們自己的循環,則意味著對Regex中的此類搜索進行隱式矢量化,并且對此類實現的任何改進也都應歸于此。這也意味著生成的代碼更簡單。可以用這樣的基準測試來查看其影響:

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private Regex _regex = new Regex("hello.*world", RegexOptions.Compiled);[Benchmark] public bool IsMatch() => _regex.IsMatch("hello. this is a test to see if it's able to find something more quickly in the world."); }

即使輸入的字符串不是特別大,也會產生可衡量的影響:

MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe71.03 ns0.308 ns0.257 ns0.47
IsMatch\netcore31\corerun.exe149.80 ns0.913 ns0.809 ns1.00

IndexOfAny最終還是.NET 5實現中的重要工具,尤其是對于FindFirstChar的實現。.NET Regex實現使用的現有優化之一是對可以開始表達式的所有可能字符進行分析。生成一個字符類,然后FindFirstChar使用該字符類對可能開始匹配的下一個位置生成搜索。這可以通過查看表達式([ab]cd|ef [g-i])jklm的生成代碼的反編譯版本來看到。與該表達式的有效匹配只能以'a','b'或'e'開頭,因此優化器生成一個字符類[abe],FindFirstChar然后使用:

Copypublic override bool FindFirstChar() {int num = runtextpos;string runtext = base.runtext;int num2 = runtextend - num;if (num2 > 0){int result;while (true){num2--;if (!RegexRunner.CharInClass(runtext[num++], "\0\u0004\0acef")){if (num2 <= 0){result = 0;break;}continue;}num--;result = 1;break;}runtextpos = num;return (byte)result != 0;}return false; }

這里需要注意的幾件事:

  • 正如前面所討論的,我們可以看到每個字符都是通過CharInClass求值的。我們可以看到傳遞給CharInClass的字符串是該類的內部可搜索表示(第一個字符表示沒有取反,第二個字符表示有四個用于表示范圍的字符,第三個字符表示沒有Unicode類別) ,然后接下來的四個字符代表兩個范圍,分別包含下限和上限。

  • 我們可以看到我們分別評估每個字符,而不是能夠一起評估多個字符。

  • 我們只看第一個字符,如果匹配,我們退出以允許引擎完全執行Go。

在.NET 5 Preview 2中,我們現在生成此代碼:

Copyprotected override bool FindFirstChar() {int runtextpos = base.runtextpos;int runtextend = base.runtextend;if (runtextpos <= runtextend - 7){ReadOnlySpan<char> readOnlySpan = runtext.AsSpan(runtextpos, runtextend - runtextpos);for (int num = 0; num < readOnlySpan.Length - 2; num++){int num2 = readOnlySpan.Slice(num).IndexOfAny('a', 'b', 'e');num = num2 + num;if (num2 < 0 || readOnlySpan.Length - 2 <= num){break;}int num3 = readOnlySpan[num + 1];if ((num3 == 'c') | (num3 == 'f')){num3 = readOnlySpan[num + 2];if (num3 < 128 && ("\0\0\0\0\0\0?\0"[num3 >> 4] & (1 << (num3 & 0xF))) != 0){base.runtextpos = runtextpos + num;return true;}}}}base.runtextpos = runtextend;return false; }

這里要注意一些有趣的事情:

  • 現在,我們使用IndexOfAny搜索三個目標字符。?IndexOfAny是矢量化的,因此它可以利用SIMD指令一次比較多個字符,并且我們為進一步優化IndexOfAny所做的任何未來改進都將隱式歸于此類FindFirstChar實現。

  • 如果IndexOfAny找到匹配項,我們不只是立即返回以給Go機會執行。相反,我們對接下來的幾個字符進行快速檢查,以增加這實際上是匹配項的可能性。在原始表達式中,您可以看到可能與第二個字符匹配的唯一值是'c'和'f',因此該實現對這些字符進行了快速比較檢查。您會看到第三個字符必須與'd'或[g-i]匹配,因此該實現將這些字符組合到單個字符類[dg-i]中,然后使用位圖對其進行評估。后兩個字符檢查都突出了我們現在為字符類發出的改進的代碼生成。

我們可以在這樣的測試中看到這種潛在的影響:

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; using System.Linq; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private static Random s_rand = new Random(42);private Regex _regex = new Regex("([ab]cd|ef[g-i])jklm", RegexOptions.Compiled);private string _input = string.Concat(Enumerable.Range(0, 1000).Select(_ => (char)('a' + s_rand.Next(26))));[Benchmark] public bool IsMatch() => _regex.IsMatch(_input); }

在我的機器上會產生以下結果:

MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe1.084 us0.0068 us0.0061 us0.08
IsMatch\netcore31\corerun.exe14.235 us0.0620 us0.0550 us1.00

先前的代碼差異也突出了另一個有趣的改進,特別是舊代碼的int num2 = runtextend-num;`` if(num2> 0)和新代碼的if(runtextpos <= runtextend-7)之間的差異。。如前所述,RegexParser將輸入模式解析為節點樹,然后對其進行分析和優化。.NET 5包括各種新的分析,有些簡單,有些更復雜。較簡單的示例之一是解析器現在將對表達式進行快速掃描,以確定是否必須有最小輸入長度才能匹配輸入。考慮一下表達式[0-9]{3}-[0-9]{2}-[0-9]{4},該表達式可用于匹配美國的社會保險號(三個ASCII數字,破折號,兩個ASCII數字,一個破折號,四個ASCII數字)。我們可以很容易地看到,此模式的任何有效匹配都至少需要11個字符;如果為我們提供了10個或更少的輸入,或者如果我們在輸入末尾找到10個字符以內卻沒有找到匹配項,那么我們可能會立即使匹配項失敗而無需進一步進行,因為這是不可能的匹配。

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Running; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private readonly Regex _regex = new Regex("[0-9]{3}-[0-9]{2}-[0-9]{4}", RegexOptions.Compiled);[Benchmark] public bool IsMatch() => _regex.IsMatch("123-45-678"); } MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe19.39 ns0.148 ns0.139 ns0.04
IsMatch\netcore31\corerun.exe459.86 ns1.893 ns1.771 ns1.00

回溯消除

.NET Regex實現當前使用回溯引擎。這種實現可以支持基于DFA的引擎無法輕松或有效地支持的各種功能,例如反向引用,并且在內存利用率以及常見情況下的吞吐量方面都非常高效。但是,回溯有一個很大的缺點,那就是可能導致退化的情況,即匹配在輸入長度上花費了指數時間。這就是.NET Regex類公開設置超時的功能的原因,因此失控匹配可能會被異常中斷。

.NET文檔提供了更多詳細信息,但可以這樣說,開發人員可以編寫正則表達式,而不會受到過多的回溯。一種方法是采用“原子組”,該原子組告訴引擎,一旦組匹配,實現就不得回溯到它,通常在這種回溯不會帶來好處的情況下使用。考慮與輸入aaaa匹配的示例表達式a+b:

  • Go引擎開始匹配a+。此操作是貪婪的,因此它匹配第一個a,然后匹配aa,然后匹配aaa,然后匹配aaaa。然后,它會顯示在輸入的末尾。

  • 沒有b匹配,因此引擎回溯1,而a+現在匹配aaa。

  • 仍然沒有b匹配,因此引擎回溯1,而a+現在匹配aa。

  • 仍然沒有b匹配,因此引擎回溯1,而a+現在匹配a。

  • 仍然沒有b可以匹配,而a+至少需要1個a,因此匹配失敗。

但是,所有這些回溯都被證明是不必要的。?a+不能匹配b可以匹配的東西,因此在這里進行大量的回溯是不會有成果的。看到這一點,開發人員可以改用表達式(?>a+)b。?(?>和)是原子組的開始和結束,它表示一旦該組匹配并且引擎經過該組,則它一定不能回溯到該組中。然后,使用我們之前針對aaaa進行匹配的示例,則將發生這種情況:

  • Go引擎開始匹配?a+。此操作是貪婪的,因此它匹配第一個a,然后匹配aa,然后匹配?aaa,然后匹配?aaaa。然后,它會顯示在輸入的末尾。

  • 沒有匹配的b,因此匹配失敗。

簡短得多,這只是一個簡單的示例。因此,開發人員可以自己進行此分析并找到手動插入原子組的位置,但是,實際上,有多少開發人員認為這樣做或花費時間呢?

相反,.NET 5現在將正則表達式作為節點樹優化階段的一部分進行分析,在發現原子組不會產生語義差異但可以幫助避免回溯的地方添加原子組。例如:

a+b將變成(?>a+)b1,因為沒有任何a+可以“回饋”與b相匹配的內容

\d+\s*將變成(?>\d+)(?>\s*),因為沒有任何可以匹配\d的東西也可以匹配\s,并且\s在表達式的末尾。

a*([xyz]|hello)將變為(?>a*)([xyz]|hello),因為在成功匹配中,a可以跟著x,y,z或h,并且沒有與任何這些重疊。

這只是.NET 5現在將執行的樹重寫的一個示例。它將進行其他重寫,部分目的是消除回溯。例如,現在它將合并彼此相鄰的各種形式的循環。考慮退化的例子a*a*a*a*a*a*a*b。在.NET 5中,現在將其重寫為功能上等效的a*b,然后根據前面的討論將其進一步重寫為(?>a*)b。這將潛在的非常昂貴的執行轉換為具有線性執行時間的執行。由于我們正在處理不同的算法復雜性,因此顯示示例基準幾乎沒有意義,但是無論如何我還是會這樣做,只是為了好玩:

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private Regex _regex = new Regex("a*a*a*a*a*a*a*b", RegexOptions.Compiled);[Benchmark] public bool IsMatch() => _regex.IsMatch("aaaaaaaaaaaaaaaaaaaaa"); } MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe379.2 ns2.52 ns2.36 ns0.000
IsMatch\netcore31\corerun.exe22,367,426.9 ns123,981.09 ns115,971.99 ns1.000

回溯減少不僅限于循環。輪換表示回溯的另一個來源,因為實現方式的匹配方式與您手動匹配時的方式類似:嘗試一個輪換分支并繼續進行,如果匹配失敗,請返回并嘗試下一個分支,依此類推。因此,減少交替產生的回溯也是有用的。

現在執行的此類重寫之一與交替前綴分解有關。考慮針對文本什么是表達式(?:this|that)的表達式。引擎將匹配內容,然后嘗試與此匹配。它不會匹配,因此它將回溯并嘗試與此匹配。但是交替的兩個分支都以th開頭。如果我們將其排除在外,然后將表達式重寫為th(?:is|at),則現在可以避免回溯。引擎將匹配,然后嘗試將th與它匹配,然后失敗,僅此而已。

這種優化還最終使更多文本暴露給FindFirstChar使用的現有優化。如果模式的開頭有多個固定字符,則FindFirstChar將使用Boyer-Moore實現在輸入字符串中查找該文本。暴露給Boyer-Moore算法的模式越大,在快速找到匹配并最小化將導致FindFirstChar退出到Go引擎的誤報中所能做的越好。通過從這種交替中拉出文本,在這種情況下,我們增加了Boyer-Moore可用的文本量。

作為另一個相關示例,.NET 5現在發現即使開發人員未指定也可以隱式錨定表達式的情況,這也有助于消除回溯。考慮用*hello匹配abcdefghijk。該實現將從位置0開始,并在該位置計算表達式。這樣做會將整個字符串abcdefghijk與.*匹配,然后從那里回溯以嘗試匹配hello,這將無法完成。引擎將使匹配失敗,然后我們將升至下一個位置。然后,引擎將把字符串bcdefghijk的其余部分與.*進行匹配,然后從那里回溯以嘗試匹配hello,這將再次失敗。等等。在這里觀察到的是,通過碰到下一個位置進行的重試通常不會成功,并且表達式可以隱式地錨定為僅在行的開頭匹配。然后,FindFirstChar可以跳過可能不匹配的位置,并避免在這些位置嘗試進行引擎匹配。

yusing BenchmarkDotNet.Attributes;using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Running; using System.Text.RegularExpressions;public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private readonly Regex _regex = new Regex(@".*text", RegexOptions.Compiled);[Benchmark] public bool IsMatch() => _regex.IsMatch("This is a test.\nDoes it match this?\nWhat about this text?"); } MethodToolchainMeanErrorStdDevRatio
IsMatch\master\corerun.exe644.1 ns3.63 ns3.39 ns0.21
IsMatch\netcore31\corerun.exe3,024.9 ns22.66 ns20.09 ns1.00

(只是為了清楚起見,許多正則表達式仍將在 .NET 5 中采用回溯,因此開發人員仍然需要謹慎運行不可信的正則表達式。)

Regex.* 靜態方法和并發

Regex類同時公開實例方法和靜態方法。靜態方法主要是為了方便起見,因為它們仍然需要在Regex實例上使用和操作。每次使用這些靜態方法之一時,該實現都可以實例化一個新的Regex并經歷完整的解析/優化/代碼生成例程,但是在某些情況下,這將浪費大量的時間和空間。相反,Regex會保留最近使用的Regex對象的緩存,并按使它們唯一的所有內容(例如,模式,RegexOptions甚至在CurrentCulture下(因為這可能會影響IgnoreCase匹配)。此緩存的大小受到限制,以Regex.CacheSize為上限,因此該實現采用了最近最少使用的(LRU)緩存:當緩存已滿并且需要添加另一個Regex時,實現將丟棄最近最少使用的項。緩存。

實現這種LRU緩存的一種簡單方法是使用鏈接列表:每次訪問某項時,它都會從列表中刪除并重新添加到最前面。但是,這種方法有一個很大的缺點,尤其是在并發世界中:同步。如果每次讀取實際上都是一個突變,則我們需要確保并發讀取(并發突變)不會破壞列表。這樣的列表正是.NET早期版本所采用的列表,并且使用了全局鎖來保護它。在.NET Core 2.1中,社區成員提交的一項不錯的更改通過允許訪問最近使用的無鎖項在某些情況下對此進行了改進,從而提高了通過靜態使用相同Regex的工作負載的吞吐量和可伸縮性。方法反復。但是,對于其他情況,實現仍然鎖定在每種用法上。

通過查看諸如Concurrency Visualizer之類的工具,可以看到此鎖定的影響,該工具是Visual Studio的擴展,可在其擴展程序庫中使用。通過在分析器下運行這樣的示例應用程序:

Copyusing System.Text.RegularExpressions; using System.Threading.Tasks;class Program {static void Main(){Parallel.Invoke(() => { while (true) Regex.IsMatch("abc", "^abc$"); },() => { while (true) Regex.IsMatch("def", "^def$"); },() => { while (true) Regex.IsMatch("ghi", "^ghi$"); },() => { while (true) Regex.IsMatch("jkl", "^jkl$"); });} }

我們可以看到這樣的圖像:

每行都是一個線程,它是此Parallel.Invoke的一部分。綠色區域是線程實際執行代碼的時間。黃色區域表示操作系統已搶占該線程的原因,因為該線程需要內核運行另一個線程。紅色區域表示線程被阻止等待某物。在這種情況下,所有紅色是因為線程正在等待Regex緩存中的共享全局鎖。

在.NET 5中,圖片看起來像這樣:

注意,沒有更多的紅色部分。這是因為緩存已被重寫為完全無鎖的讀取;唯一獲得鎖的時間是將新的Regex添加到緩存中,但是即使發生這種情況,其他線程也可以繼續從緩存中讀取實例并使用它們。這意味著,只要為應用程序及其常規使用的Regex靜態方法正確調整Regex.CacheSize的大小,此類訪問將不再招致它們過去的延遲。到今天為止,該值默認為15,但是該屬性具有設置器,因此可以對其進行更改以更好地滿足應用程序的需求。

靜態方法的分配也得到了改進,方法是精確地更改緩存內容,從而避免分配不必要的包裝對象。我們可以通過上一個示例的修改版本看到這一點:

Copyusing System.Text.RegularExpressions; using System.Threading.Tasks;class Program {static void Main(){Parallel.Invoke(() => { for (int i = 0; i < 10_000; i++) Regex.IsMatch("abc", "^abc$"); },() => { for (int i = 0; i < 10_000; i++) Regex.IsMatch("def", "^def$"); },() => { for (int i = 0; i < 10_000; i++) Regex.IsMatch("ghi", "^ghi$"); },() => { for (int i = 0; i < 10_000; i++) Regex.IsMatch("jkl", "^jkl$"); });} }

使用Visual Studio中的.NET對象分配跟蹤工具運行它。左邊是.NET Core 3.1,右邊是.NET 5 Preview 2:

特別要注意的是,左側包含40,000個分配的行,而右側只有4個。

其他開銷減少

我們已經介紹了.NET 5中對正則表達式進行的一些關鍵改進,但該列表絕不是完整的。到處都有一些較小的優化清單,盡管我們不能在這里列舉所有的優化清單,但我們可以逐步介紹更多。

在某些地方,我們已經采用了前面討論過的矢量化形式。例如,當使用RegexOptions.Compiled且該模式包含一個字符串字符串時,編譯器將分別檢查每個字符。如果查看諸如abcd之類的表達式的反編譯代碼,就會看到以下內容:

if (4 <= runtextend - runtextpos &&runtext[runtextpos] == 'a' &&runtext[runtextpos + 1] == 'b' &&runtext[runtextpos + 2] == 'c' &&runtext[runtextpos + 3] == 'd')

在.NET 5中,當使用DynamicMethod創建編譯后的代碼時,我們現在嘗試比較Int64值(在64位系統上,或在32位系統上比較Int32),而不是比較單個字符。這意味著對于上一個示例,我們現在改為生成與此類似的代碼:

if (3u < (uint)readOnlySpan.Length && *(long*)readOnlySpan._pointer == 28147922879250529L)

(我說“類似”,因為我們無法在C#中表示生成的確切IL,這與使用Unsafe類型的成員更加一致。)我們這里不必擔心字節順序問題,因為生成用于比較的Int64/Int32值的代碼與加載用于比較的輸入值的同一臺計算機(甚至在同一進程中)發生。

另一個示例是先前在先前生成的代碼示例中實際顯示的內容,但已被掩蓋。在比較@"a\sb"表達式的輸出時,您可能之前已經注意到,以前的代碼包含對CheckTimeout()的調用,但是新代碼沒有。此CheckTimeout()函數用于檢查我們的執行時間是否超過了Regex構造時提供給其的超時值所允許的時間。但是,在沒有提供超時的情況下使用的默認超時是“無限”,因此“無限”是非常常見的值。由于我們永遠不會超過無限超時,因此當我們為RegexOptions.Compiled正則表達式編譯代碼時,我們會檢查超時,如果是無限超時,則跳過生成這些CheckTimeout()調用。

在其他地方也存在類似的優化。例如,默認情況下,Regex執行區分大小寫的比較。僅在指定RegexOptions.IgnoreCase的情況下(或者表達式本身包含執行不區分大小寫的匹配的指令)才使用不區分大小寫的比較,并且僅當使用不區分大小寫的比較時,我們才需要訪問CultureInfo.CurrentCulture以確定如何進行比較。此外,如果指定了RegexOptions.InvariantCulture,則我們也無需訪問CultureInfo.CurrentCulture,因為它將永遠不會使用。所有這些意味著,如果我們證明不再需要它,則可以避免生成訪問CultureInfo.CurrentCulture的代碼。最重要的是,我們可以通過發出對char.ToLowerInvariant而不是char.ToLower(CultureInfo.InvariantCulture)的調用來使RegexOptions.InvariantCulture更快,尤其是因為.NET 5中ToLowerInvariant也得到了改進(還有另一個示例,其中將Regex更改為使用其他框架功能時,只要我們改進這些已利用的功能,它就會隱式受益。

另一個有趣的更改是Regex.Replace和Regex.Split。這些方法被實現為對Regex.Match的封裝,將其功能分層。但是,這意味著每次找到匹配項時,我們都將退出掃描循環,逐步遍歷抽象的各個層次,在匹配項上執行工作,然后調回引擎,以正確的方式進行工作返回到掃描循環,依此類推。最重要的是,每個匹配項都需要創建一個新的Match對象。現在在.NET 5中,這些方法在內部使用了一個專用的基于回調的循環,這使我們能夠停留在嚴格的掃描循環中,并一遍又一遍地重用同一個Match對象(如果公開公開,這是不安全的,但是可以作為內部實施細節來完成)。在實現“替換”中使用的內存管理也已調整為專注于跟蹤要替換或不替換的輸入區域,而不是跟蹤每個單獨的字符。這樣做的最終結果可能對吞吐量和內存分配都產生相當大的影響,尤其是對于輸入量非常長且替換次數很少的輸入。

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.Linq; using System.Text.RegularExpressions;[MemoryDiagnoser] public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private Regex _regex = new Regex("a", RegexOptions.Compiled);private string _input = string.Concat(Enumerable.Repeat("abcdefghijklmnopqrstuvwxyz", 1_000_000));[Benchmark] public string Replace() => _regex.Replace(_input, "A"); } MethodToolchainMeanErrorStdDevRatioGen 0Gen 1Gen 2Allocated
Replace\master\corerun.exe93.79 ms1.120 ms0.935 ms0.4581.59 MB
Replace\netcore31\corerun.exe209.59 ms3.654 ms3.418 ms1.0033666.6667666.6667666.6667371.96 MB

看看效果

所有這些結合在一起,可以在各種基準上產生明顯更好的性能。為了說明這一點,我在網上搜索了正則表達式基準并進行了幾次測試。

mariomka/regex-benchmark的基準測試已經具有C#版本,因此簡單地編譯和運行這很容易:

Copyusing System; using System.IO; using System.Text.RegularExpressions; using System.Diagnostics;class Benchmark {static void Main(string[] args){if (args.Length != 1){Console.WriteLine("Usage: benchmark <filename>");Environment.Exit(1);}StreamReader reader = new System.IO.StreamReader(args[0]);string data = reader.ReadToEnd();// EmailBenchmark.Measure(data, @"[\w\.+-]+@[\w\.-]+\.[\w\.-]+");// URIBenchmark.Measure(data, @"[\w]+://[^/\s?#]+[^\s?#]+(?:\?[^\s#]*)?(?:#[^\s]*)?");// IPBenchmark.Measure(data, @"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9])");}static void Measure(string data, string pattern){Stopwatch stopwatch = Stopwatch.StartNew();MatchCollection matches = Regex.Matches(data, pattern, RegexOptions.Compiled);int count = matches.Count;stopwatch.Stop();Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds.ToString("G", System.Globalization.CultureInfo.InvariantCulture) + " - " + count);} }

在我的機器上,這是使用.NET Core 3.1的控制臺輸出:

Copy966.9274 - 92 746.3963 - 5301 65.6778 - 5

以及使用.NET 5的控制臺輸出:

Copy274.3515 - 92 159.3629 - 5301 15.6075 - 5

破折號前的數字是執行時間,破折號后的數字是答案(因此,第二個數字保持不變是一件好事)。執行時間急劇下降:分別提高了3.5倍,4.6倍和4.2倍!

我還找到了?https://zherczeg.github.io/sljit/regex_perf.html,它具有各種基準,但沒有C#版本。?我將其轉換為Benchmark.NET測試:

Copyusing BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System.IO; using System.Text.RegularExpressions;[MemoryDiagnoser] public class Program {static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);private static string s_input = File.ReadAllText(@"d:\mtent12.txt");private Regex _regex;[GlobalSetup]public void Setup() => _regex = new Regex(Pattern, RegexOptions.Compiled);[Params(@"Twain",@"(?i)Twain",@"[a-z]shing",@"Huck[a-zA-Z]+|Saw[a-zA-Z]+",@"\b\w+nn\b",@"[a-q][^u-z]{13}x",@"Tom|Sawyer|Huckleberry|Finn",@"(?i)Tom|Sawyer|Huckleberry|Finn",@".{0,2}(Tom|Sawyer|Huckleberry|Finn)",@".{2,4}(Tom|Sawyer|Huckleberry|Finn)",@"Tom.{10,25}river|river.{10,25}Tom",@"[a-zA-Z]+ing",@"\s[a-zA-Z]{0,12}ing\s",@"([A-Za-z]awyer|[A-Za-z]inn)\s")]public string Pattern { get; set; }[Benchmark] public bool IsMatch() => _regex.IsMatch(s_input); }

并對照該頁面提供的大約20MB文本文件輸入運行它,得到以下結果:

MethodToolchainPatternMeanRatio
IsMatch\master\corerun.exe(?i)T(…)Finn [31]12,703.08 ns0.32
IsMatch\netcore31\corerun.exe(?i)T(…)Finn [31]40,207.12 ns1.00
IsMatch\master\corerun.exe(?i)Twain159.81 ns0.84
IsMatch\netcore31\corerun.exe(?i)Twain189.49 ns1.00
IsMatch\master\corerun.exe([A-Z(…)nn)\s [29]6,903,345.70 ns0.10
IsMatch\netcore31\corerun.exe([A-Z(…)nn)\s [29]67,388,775.83 ns1.00
IsMatch\master\corerun.exe.{0,2(…)Finn) [35]1,311,160.79 ns0.68
IsMatch\netcore31\corerun.exe.{0,2(…)Finn) [35]1,942,021.93 ns1.00
IsMatch\master\corerun.exe.{2,4(…)Finn) [35]1,202,730.97 ns0.67
IsMatch\netcore31\corerun.exe.{2,4(…)Finn) [35]1,790,485.74 ns1.00
IsMatch\master\corerun.exeHuck[(…)A-Z]+ [26]282,030.24 ns0.01
IsMatch\netcore31\corerun.exeHuck[(…)A-Z]+ [26]19,908,290.62 ns1.00
IsMatch\master\corerun.exeTom.{(…)5}Tom [33]8,817,983.04 ns0.09
IsMatch\netcore31\corerun.exeTom.{(…)5}Tom [33]94,075,640.48 ns1.00
IsMatch\master\corerun.exeTomS(…)Finn [27]39,214.62 ns0.14
IsMatch\netcore31\corerun.exeTomS(…)Finn [27]281,452.38 ns1.00
IsMatch\master\corerun.exeTwain64.44 ns0.77
IsMatch\netcore31\corerun.exeTwain83.61 ns1.00
IsMatch\master\corerun.exe[a-q][^u-z]{13}x1,695.15 ns0.09
IsMatch\netcore31\corerun.exe[a-q][^u-z]{13}x19,412.31 ns1.00
IsMatch\master\corerun.exe[a-zA-Z]+ing3,042.12 ns0.31
IsMatch\netcore31\corerun.exe[a-zA-Z]+ing9,896.25 ns1.00
IsMatch\master\corerun.exe[a-z]shing28,212.30 ns0.24
IsMatch\netcore31\corerun.exe[a-z]shing117,954.06 ns1.00
IsMatch\master\corerun.exe\b\w+nn\b32,278,974.55 ns0.21
IsMatch\netcore31\corerun.exe\b\w+nn\b152,395,335.00 ns1.00
IsMatch\master\corerun.exe\s[a-(…)ing\s [21]1,181.86 ns0.23
IsMatch\netcore31\corerun.exe\s[a-(…)ing\s [21]5,161.79 ns1.00

這些比例中的一些非常有趣。

另一個是“The Computer Language Benchmarks Game”中的“ regex-redux”基準。在dotnet/performance回購中利用了此實現,因此我運行了該代碼:

MethodToolchainoptionsMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Gen 1Gen 2Allocated
RegexRedux_5\master\corerun.exeCompiled7.941 ms0.0661 ms0.0619 ms7.965 ms7.782 ms8.009 ms0.300.012.67 MB
RegexRedux_5\netcore31\corerun.exeCompiled26.311 ms0.5058 ms0.4731 ms26.368 ms25.310 ms27.198 ms1.000.001571.428612.19 MB

因此,在此基準上,.NET 5的吞吐量是.NET Core 3.1的3.3倍。

呼吁社區行動

我們希望您的反饋和貢獻有多種方式。

下載.NET 5 Preview 2并使用正則表達式進行嘗試。您看到可衡量的收益了嗎?如果是這樣,請告訴我們。如果沒有,也請告訴我們,以便我們共同努力,為您最有價值的表達方式改善效果。

是否有對您很重要的特定正則表達式?如果是這樣,請與我們分享;我們很樂意使用來自您的真實正則表達式,您的輸入數據以及相應的預期結果來擴展我們的測試套件,以幫助確保在對我們進行進一步改進時,不會退回對您而言重要的事情代碼庫。實際上,我們歡迎PR到dotnet/runtime來以這種方式擴展測試套件。您可以看到,除了成千上萬個綜合測試用例之外,Regex測試套件還包含大量示例,這些示例來自文檔,教程和實際應用程序。如果您認為應該在此處添加表達式,請提交PR。作為性能改進的一部分,我們已經更改了很多代碼,盡管我們一直在努力進行驗證,但是肯定會漏入一些錯誤。您對自己的重要表達的反饋將有助于您實現這一目標!

與 .NET 5中已經完成的工作一樣,我們還列出了可以探索的其他已知工作的清單,這些工作已編入dotnet/runtime#1349。我們將在這里歡迎其他建議,更歡迎在此處概述的一些想法的實際原型設計或產品化(通過適當的性能審查,測試等)。一些示例:

  • 改進自動添加原子組的循環。如本文所述,我們現在自動在多個位置插入原子組,我們可以檢測到它們可能有助于減少回溯,同時保持語義相同。我們知道,但是,我們的分析存在一些空白,填補這些空白非常好。例如,該實現現在將a*b+c更改為(?>a*)(?>b+)c,因為它將看到b+不會提供任何可以匹配c的東西,而a*不會給出可以匹配b的任何東西(b+表示必須至少有一個b)。但是,即使后者合適,表達式a*b*c也會轉換為a*(?>b*)c而不是(?>a*)(?>b*)c。這里的問題是,我們目前僅查看序列中的下一個節點,并且b*可能匹配零項,這意味著a*之后的下一個節點可能是c,而我們目前的眼光并不那么遠。

  • 改進原子基團自動交替添加的功能。根據對交替的分析,我們可以做更多的工作來將交替自動升級為原子。例如,給定類似(Bonjour|Hello), .*的表達式,我們知道,如果Bonjour匹配,則Hello也不可能匹配,因此可以將這種替換設置為原子的。

  • 改善IndexOfAny的向量化。如本文所述,我們現在盡可能使用內置函數,這樣對這些表達式的改進也將使Regex受益(除了使用它們的所有其他工作負載)。現在,我們在某些正則表達式中對IndexOfAny的依賴度很高,以至于它可以代表處理的很大一部分,例如在前面顯示的“ regex redux”基準上,約有30%的時間花費在IndexOfAny上。這里有機會改進此功能,從而也改進Regex。這由?dotnet/runtime#25023?單獨引入。

  • 制作DFA實現原型。.NET正則表達式支持的某些方面很難使用基于DFA的正則表達式引擎來完成,但是某些操作應該是可以實現的,而不必擔心。例如,Regex.IsMatch不必關心捕獲語義(.NET在捕獲方面有一些額外的功能,這使其比其他實現更具挑戰性),因此,如果該表達式不包含諸如反向引用之類的問題構造,或環顧四周,對于IsMatch,我們可以探索使用基于DFA的引擎,并且有可能隨著時間的推移而得到更廣泛的使用。

  • 改善測試。如果您對測試的興趣超過對實施的興趣,那么在這里也需要做一些有價值的事情。我們的代碼覆蓋率已經很高,但是仍然存在差距。插入這些代碼(并可能在該過程中找到無效代碼)將很有幫助。查找并合并其他經過適當許可的測試套件以提供更多涵蓋各種表達式的內容也很有價值。

謝謝閱讀,翻譯自?Regex Performance Improvements in .NET 5

總結

以上是生活随笔為你收集整理的.NET 5 中的正则引擎性能改进(翻译)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

免费视频你懂得 | 欧美综合久久久 | 久久电影网站中文字幕 | 免费观看一区二区三区视频 | 91免费看黄色 | 亚洲理论在线观看 | 欧美影片| 草樱av| 天天射狠狠干 | 四虎成人精品永久免费av | 欧美另类z0zx| 岛国精品一区二区 | 亚洲va欧洲va国产va不卡 | 在线a视频| 日韩在线视频网站 | 婷婷亚洲激情 | www最近高清中文国语在线观看 | 久久一久久 | 免费久久片 | 九九免费在线观看 | 日韩精品久久一区二区三区 | 日韩丝袜在线观看 | 成人性生交大片免费观看网站 | 日韩精品中字 | 欧美日韩中文国产一区发布 | 人人精品 | 久久久精品福利视频 | 亚洲天堂网在线观看视频 | 久久亚洲电影 | 综合久久2023| 色婷婷激情四射 | 天天爱天天草 | 国产精品成人av在线 | 国产超碰在线观看 | 国产99久 | 激情五月五月婷婷 | 99亚洲天堂 | 国产麻豆成人传媒免费观看 | 91麻豆精品国产91久久久无限制版 | 91久久久久久久 | 探花视频免费观看高清视频 | 日韩在线视 | 日韩av网页 | 久久国产精品99久久久久 | av黄色在线播放 | 日韩免费成人 | 国产精品18久久久 | 久久久久国产a免费观看rela | 少妇高潮流白浆在线观看 | 在线观看视频黄色 | 国产精品一二三 | 免费观看的av网站 | 国产精品久久久一区二区 | 9色在线视频 | 亚洲成人频道 | 国产精品视频免费在线观看 | 国产午夜麻豆影院在线观看 | 人人添人人澡人人澡人人人爽 | 91在线精品秘密一区二区 | 亚洲欧美日本一区二区三区 | 国产91在| 日本aaa在线观看 | 91视频电影 | 日韩av高清| 中文国产字幕在线观看 | 国产免费叼嘿网站免费 | 久久久精品网站 | 国产精品久久久久久久久久 | 久久99九九99精品 | 亚洲www天堂com | 天天干夜夜爱 | 色播五月激情综合网 | 99久久爱 | 国产精品视频不卡 | 91视频中文字幕 | 国产精品国产三级在线专区 | 精品久久久久久国产偷窥 | 精品久久1 | 国产成人av一区二区三区在线观看 | 四虎影视精品永久在线观看 | 国产精品视频线看 | 亚洲 欧美 日韩 综合 | 精品国产伦一区二区三区免费 | 日韩大片在线播放 | 日韩一区二区三区不卡 | 久久免费毛片 | 日韩一级电影在线观看 | 91三级在线观看 | 97碰碰视频| 91在线一区二区 | 国产福利a | 伊人资源视频在线 | 九九免费观看视频 | 欧美一级黄色片 | 国产欧美日韩精品一区二区免费 | 国产日韩欧美在线 | 日韩电影在线一区 | 日韩免费一级a毛片在线播放一级 | 日韩精品一区二区三区在线播放 | 97国产电影 | 久久精选 | 美女黄濒| 日韩在线免费播放 | 久久视频一区 | 日韩中文字幕亚洲一区二区va在线 | av在线超碰 | 狠狠干激情 | 日本黄色免费观看 | 天天做日日做天天爽视频免费 | www黄色软件 | 日韩三级精品 | 99久久er热在这里只有精品15 | 99国产成+人+综合+亚洲 欧美 | 97免费在线观看视频 | 成人网在线免费视频 | 国产精品伦一区二区三区视频 | 在线看av的网址 | 国产成人一区二区三区 | 一区二区三区免费在线播放 | 中文字幕在线观看免费高清完整版 | 麻豆 free xxxx movies hd | 西西444www大胆无视频 | 欧洲精品码一区二区三区免费看 | 亚洲欧洲成人 | 9色在线视频 | 国产淫片| 亚洲九九九在线观看 | 国产在线国偷精品产拍免费yy | 国产日本亚洲高清 | av福利在线看 | 在线观看国产成人av片 | av成人在线观看 | 精品国产不卡 | 最新国产在线 | 在线播放 亚洲 | av888.com| 成人毛片一区 | 中文字幕在线观看免费高清电影 | 国产欧美在线一区 | 久久免费视频网站 | 国产视频每日更新 | 精品国产视频一区 | 婷婷久久国产 | 91一区二区三区在线观看 | 欧美美女视频在线观看 | 伊人热 | 婷婷激情综合 | 国产高清av免费在线观看 | 欧美精品久久久久久久亚洲调教 | 欧美一区二区三区在线观看 | 亚洲三级黄色 | 五月婷婷丁香网 | 激情自拍av | 亚洲精品成人免费 | 特黄色大片 | 久久精品免费观看 | 超碰在线个人 | 色综合咪咪久久网 | 99视频精品在线 | 国产综合精品久久 | 欧美大片第1页 | 黄网站大全 | 韩国精品视频在线观看 | www久| 91试看| 国产一区二区三区在线 | 欧美福利网站 | 欧美午夜理伦三级在线观看 | 国产99爱 | 狠狠色丁香久久婷婷综 | 手机在线观看国产精品 | 国产精品伦一区二区三区视频 | 免费在线日韩 | 婷婷色亚洲 | www.色午夜.com | 日日天天 | 在线黄色国产电影 | 日本中文字幕在线一区 | 欧美激情视频一二区 | 国产精品久久久久久久久久久久久 | 久久国产福利 | 久久成人午夜 | 五月天色综合 | 国产r级在线观看 | 国产精品麻豆三级一区视频 | 狠狠操夜夜操 | 国产一卡久久电影永久 | 久久伦理电影 | 国产成人在线一区 | 久久99久久99免费视频 | www.xxx.性狂虐 | 丁香婷婷激情 | 日本精品久久久一区二区三区 | 久久香蕉电影 | 婷婷激情站| 999久久国产精品免费观看网站 | 天天干国产 | 国产精品久久一 | 亚洲综合一区二区精品导航 | 在线观看小视频 | 欧美日韩不卡在线 | 国产 日韩 欧美 中文 在线播放 | 色激情五月| 99久久99视频只有精品 | 日韩在线一区二区免费 | 成人中文字幕在线 | 免费a级毛片在线看 | 三三级黄色片之日韩 | 97视频在线观看免费 | 国产成人黄色 | 国产亚洲精品久 | 国产精品观看视频 | 伊人国产视频 | 国产精品theporn | 欧美成天堂网地址 | 久久久精品国产一区二区电影四季 | 国产又粗又长的视频 | 久久精品国产一区二区 | 国产亚洲视频在线免费观看 | 国产 精品 资源 | 亚洲 欧洲av| 日本亚洲国产 | 久热只有精品 | www.av在线.com | 夜夜骑首页 | 国产伦精品一区二区三区免费 | 日韩av福利在线 | 久久毛片网站 | 波多野结衣在线视频一区 | 国产一区二区三区免费在线观看 | 特级西西444www高清大视频 | 免费在线观看午夜视频 | 另类五月激情 | 99情趣网视频 | 国产在线国产 | 九九色综合 | 香蕉网站在线观看 | 一区二区久久 | 免费高清无人区完整版 | 在线观看视频一区二区三区 | 国产一区二区久久久 | 一区二区精品在线视频 | 国产96视频 | 97视频人人免费看 | www视频在线播放 | 成人久久18免费网站麻豆 | 国产一区久久 | 久久性生活片 | 国产福利在线免费观看 | 国产精品第二十页 | 很污的网站 | 国产高清在线免费观看 | 91在线操| 在线观看中文字幕亚洲 | av观看在线观看 | 国产精品视频永久免费播放 | 国产丝袜制服在线 | 91av中文| 国产一区在线视频观看 | 91秒拍国产福利一区 | 在线国产高清 | 日韩精品黄 | 亚洲精品网址在线观看 | 国产不卡一 | 精品视频久久久久久 | 久久久国产99久久国产一 | 成人动图| 久久欧美综合 | 久久婷婷久久 | 国产午夜剧场 | 成人蜜桃网 | 天天做日日爱夜夜爽 | 91在线免费观看网站 | 7777xxxx| 免费一级片在线观看 | 97超碰中文字幕 | 成人黄色片免费看 | 国产美女主播精品一区二区三区 | 国产福利电影网址 | 日本精品小视频 | 999免费视频 | 久久欧洲视频 | 日本视频网 | 黄色小说视频在线 | 精品一区二区在线免费观看 | 国产涩涩网站 | 99久久精品一区二区成人 | 最近的中文字幕大全免费版 | 精品国偷自产国产一区 | 99精品欧美一区二区蜜桃免费 | 欧美伦理电影一区二区 | 九九视频精品在线 | 国产精品嫩草影视久久久 | 天天综合网久久综合网 | 亚洲乱码一区 | 81国产精品久久久久久久久久 | 亚洲国产av精品毛片鲁大师 | 国产精品热视频 | 黄色a三级| 在线观看国产日韩欧美 | 日本成人黄色片 | 亚洲国产精品va在线看黑人动漫 | 美女av免费看 | av一级在线 | 欧美国产日韩一区二区三区 | 国产三级精品三级在线观看 | 日韩国产欧美在线播放 | 国产精品99久久久久 | 免费看黄20分钟 | 伊甸园永久入口www 99热 精品在线 | 天天操天天干天天 | 天天干,夜夜爽 | 国产最新在线视频 | 久久成人亚洲欧美电影 | 国产一二区精品 | 最新日本中文字幕 | 91九色蝌蚪视频在线 | 超碰人在线 | 久久公开视频 | 欧美日韩亚洲在线观看 | 亚洲综合色丁香婷婷六月图片 | av理论电影 | 午夜美女福利直播 | 88av色 | 麻豆传媒视频观看 | 91亚洲精品久久久蜜桃借种 | 伊人狠狠色 | 波多野结衣精品 | freejavvideo日本免费 | 亚洲国产成人精品电影在线观看 | 国产精品久久久久久久99 | 少妇bbr搡bbb搡bbb | 久久久午夜精品理论片中文字幕 | 激情久久久久久久久久久久久久久久 | 国产中文字幕一区 | 亚洲精品色视频 | 国内外成人在线视频 | 在线99| 99国产精品| 国产淫片免费看 | www.五月天婷婷.com | 精品久久久久久电影 | 国产精品99久久久久久大便 | 99久久精品国产亚洲 | 日本久久视频 | 成人精品视频久久久久 | 日韩精品中文字幕在线不卡尤物 | jizz18欧美18| 亚洲综合在线五月天 | 免费观看久久久 | 99热最新在线 | 免费观看丰满少妇做爰 | 久久婷婷国产色一区二区三区 | 超碰在线免费福利 | 日本少妇视频 | 一区二区三区中文字幕在线观看 | 免费观看日韩 | 亚洲精品乱码白浆高清久久久久久 | 手机在线看永久av片免费 | 久久久久中文 | 91视频免费看片 | 999在线视频 | 99热这里只有精品在线观看 | 综合精品在线 | 国产精品成人一区二区 | 在线黄色国产 | 国产91国语对白在线 | 日日干日日色 | 国产不卡在线播放 | 国产高清成人 | 91麻豆免费看| 91香蕉视频污在线 | 国产视频资源在线观看 | 色噜噜在线观看视频 | 国产三级视频在线 | 中文字幕在线观看完整版 | 中文字幕第一 | 夜夜躁日日躁狠狠久久88av | 亚洲欧美在线观看视频 | 99久久综合狠狠综合久久 | 国产手机视频在线 | 国产一区二区精 | 欧美91av| 久久99视频 | 青青河边草免费直播 | 麻豆视频免费播放 | 国产精品福利在线 | 中文字幕在线观看网 | 天天综合网天天 | 天天操夜夜操天天射 | 香蕉视频91| 国产精品国产三级国产aⅴ无密码 | 美女网站视频色 | 久久久私人影院 | 国产 一区二区三区 在线 | 中文字幕色婷婷在线视频 | 亚洲精选在线观看 | 国产欧美精品一区二区三区 | 97综合视频 | 香蕉视频免费在线播放 | 日韩大片在线看 | 欧美亚洲另类在线视频 | www.伊人网 | av网站免费在线 | 深夜免费福利在线 | 国产成人av免费在线观看 | 色久av | 久久久久女人精品毛片 | 亚洲欧洲精品视频 | 欧美日韩中文在线视频 | 91亚洲精品在线 | 青草视频在线看 | 国产精品美女久久久久久久 | 91视频 - v11av | 综合色婷婷 | 亚洲欧美婷婷六月色综合 | 国产精品一级在线 | 亚洲性少妇性猛交wwww乱大交 | 色视频在线 | 午夜国产福利在线 | 精品视频在线看 | 久久久久北条麻妃免费看 | 人人精品| 97人人精品 | 亚洲日本va中文字幕 | www,黄视频 | 激情www| 久久高清视频免费 | 天天操天天射天天插 | 在线v片 | 天天综合91 | 午夜av一区 | 国产精品久久久久久久久久久久午夜片 | 亚洲精品欧美专区 | 欧美亚洲国产一卡 | 久久久久久久久久久久av | 一级黄色片在线免费观看 | 国产精品视频你懂的 | 国产成人黄色在线 | 日本久久片 | 激情综合色播五月 | 亚洲aaa级 | av免费在线看网站 | av在线播放不卡 | 婷婷电影网| 美女av免费看| av电影亚洲 | 夜色.com| 日本久久影视 | 91探花系列在线播放 | 99久久精品视频免费 | 午夜性生活 | 在线观看免费高清视频大全追剧 | 99国产一区二区三精品乱码 | 国产一区二区三区免费在线观看 | 999国内精品永久免费视频 | 伊人婷婷 | 91视频com| 国产精久久 | 视频一区视频二区在线观看 | 亚州精品一二三区 | 国产精品美女免费视频 | 国产精品久免费的黄网站 | 国产精品久久久久久久久久久久午夜片 | 91av在线看 | 国产精品不卡av | 欧美日韩国产一二三区 | 欧美在线视频一区二区 | 欧美日韩午夜 | 精品久久五月天 | 精品一区二区三区在线播放 | 狠狠操狠狠操 | 在线看一级片 | www.天天成人国产电影 | 国产69久久精品成人看 | 欧美男男tv网站 | 中文字幕高清有码 | 精品久久久久_ | 欧洲亚洲精品 | 天天操夜夜逼 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | www.91国产| 欧美精品一区二区免费 | 少妇搡bbb| 99久久精品久久久久久动态片 | 国产高清一级 | 911av视频 | 福利视频网站 | 欧美精品中文字幕亚洲专区 | 中文字幕一区二区三区在线播放 | 久久婷婷精品视频 | 久草在线免费看视频 | 国产视频每日更新 | 久久色视频| 成人在线超碰 | 精品国产乱码久久久久 | 国产午夜三级一区二区三 | 免费看污片 | 久久乐九色婷婷综合色狠狠182 | 久久久亚洲麻豆日韩精品一区三区 | 波多野结衣综合网 | 久久亚洲在线 | 福利电影久久 | 99视频精品 | 五月婷婷视频在线 | 国产字幕在线观看 | 岛国大片免费视频 | 国产又粗又猛又黄又爽的视频 | 天天干天天做 | 国产亚洲视频系列 | 精品国产亚洲日本 | 高清不卡一区二区在线 | 中文字幕一区二区三区久久 | 97色资源 | 成人午夜精品 | a级片在线播放 | 91在线视频观看免费 | www色综合| 亚洲女裸体 | 美女黄色网在线播放 | 欧美日韩免费在线视频 | 开心激情五月婷婷 | 久草在线久草在线2 | 伊人电影在线观看 | www,黄视频 | 特级毛片在线 | 日韩欧美一区二区三区视频 | 日韩美av在线| 日韩午夜三级 | 欧美日比视频 | www五月天婷婷 | 日韩午夜高清 | 国产色爽| 狠狠狠狠狠操 | 婷婷精品国产一区二区三区日韩 | 天天操夜夜想 | 欧洲不卡av| 99色在线观看 | 91精品国产一区 | 国产视频在线观看一区二区 | 人成在线免费视频 | 亚洲精品黄色在线观看 | 中文字幕人成人 | 激情五月开心 | 91资源在线| 国产一二区在线观看 | avav片| av网站在线观看免费 | 丁香一区二区 | 久久好看| 男女全黄一级一级高潮免费看 | 又黄又色又爽 | 国产精品欧美 | 在线v片免费观看视频 | 国产一区视频在线 | 天天爱av导航 | 国产91免费在线观看 | 91av免费在线观看 | 在线观看精品视频 | 精精国产xxxx视频在线播放 | 日韩视频在线观看视频 | 国产无套一区二区三区久久 | 精品久久久久久久久久久久久久久久 | 亚洲国产成人精品久久 | 久久综合九色综合97_ 久久久 | 深夜男人影院 | 超级碰碰碰免费视频 | 久久免费视频一区 | 一级黄色网址 | 国产色在线,com | 色婷婷久久久 | 久久精品免费 | 韩国一区二区三区在线观看 | 97成人精品视频在线播放 | 精品国产自 | 国产精品99久久久久久久久 | 99久久999久久久精玫瑰 | av免费在线网 | 国产精品美女在线 | 亚洲精品在线观看网站 | 日韩av资源在线观看 | 天天干天天看 | 亚洲综合网 | 亚洲成人av在线 | 欧美aa在线| 亚洲欧美日韩国产精品一区午夜 | www.av免费观看 | 粉嫩av一区二区三区免费 | 国产精品欧美一区二区三区不卡 | 中文成人字幕 | 免费三级黄色 | 久久99久久99免费视频 | 亚洲欧美激情精品一区二区 | www.夜色.com | 国产亚洲精品成人av久久影院 | 久久精品xxx| 毛片的网址 | 日韩国产精品久久久久久亚洲 | 成人观看| 91在线视频 | 黄色国产在线观看 | 中文日韩在线视频 | 97福利| 亚洲丁香久久久 | 日韩电影黄色 | 久久视频免费观看 | 美女久久久 | 一级成人网 | 色激情五月 | 成 人 免费 黄 色 视频 | 精品国产精品久久 | 最近日本字幕mv免费观看在线 | 国产精品色婷婷 | 色婷婷六月天 | 日韩一区二区三免费高清在线观看 | 国产精品九九热 | 黄色在线免费观看网站 | 99国产精品免费网站 | 五月婷激情 | 中文字幕在线久一本久 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 中文字幕日韩免费视频 | 在线之家免费在线观看电影 | 久久久久久中文字幕 | 成年人视频免费在线播放 | 99夜色| 久久一区国产 | 草久中文字幕 | 久久 地址 | 国产一区国产二区在线观看 | 色综合天天视频在线观看 | 人人视频网站 | 操操爽 | 婷婷色五 | 色综合久久精品 | 成人欧美日韩国产 | bbb搡bbb爽爽爽| 日韩在线高清免费视频 | 日韩一级黄色av | 久久亚洲影院 | 亚欧日韩成人h片 | 色综合久久88色综合天天6 | 国产精品五月天 | 玖玖在线播放 | 人人涩 | 二区三区视频 | 国产四虎在线 | 日韩在线视频看看 | 久久天天操 | 久久精品免视看 | 极品国产91在线网站 | 国产在线a| 精品亚洲免费视频 | 欧美亚洲一区二区在线 | 国产99久久久精品 | 91资源在线免费观看 | 91热在线| 午夜精品一区二区三区免费视频 | 亚洲精品午夜一区人人爽 | 国产91在线播放 | 99亚洲精品 | 一本色道久久精品 | 国产美女久久久 | 九九色视频 | 五月婷婷欧美视频 | 日韩三级不卡 | 在线观看精品一区 | 日韩av中文字幕在线免费观看 | 精品1区2区 | wwwww.国产 | 国产成人久 | 久久精品牌麻豆国产大山 | 精品久久久久久久久中文字幕 | 久久综合色婷婷 | 美女黄网站视频免费 | 久久久黄色免费网站 | 97人人超| 最新国产福利 | 国产高清在线视频 | 国产精品video爽爽爽爽 | 涩涩网站在线 | 久久精品国产成人 | 午夜精品麻豆 | 国产在线国产 | 人人干97 | 国产精品白虎 | 又爽又黄又刺激的视频 | 337p日本欧洲亚洲大胆裸体艺术 | 国产精品久久久久久久毛片 | 九九久久视频 | 免费看一级特黄a大片 | 国产一级片免费播放 | 国产精品国产三级国产专区53 | 97在线视频免费看 | 99精品热视频只有精品10 | 午夜av大片| 精品国产视频在线观看 | 麻豆视频国产在线观看 | 国产精品久久久久久久7电影 | 国产精品粉嫩 | 亚洲国产成人在线观看 | 偷拍视频一区 | 午夜影院一级片 | 久久精品视频免费播放 | 日韩xxx视频 | 国产.精品.日韩.另类.中文.在线.播放 | 国产一区91| 人人看人人| 青青草国产在线 | 97国产在线观看 | 狠狠操导航 | 亚洲播播| 日韩激情免费视频 | 日韩综合第一页 | 日操干| 国产美女被啪进深处喷白浆视频 | 国产福利在线不卡 | 狠狠狠狠狠狠狠狠干 | 人人爽夜夜爽 | 国产精品久久99综合免费观看尤物 | 国产精品成人一区二区三区吃奶 | 国产视频一区二区三区在线 | 免费a现在观看 | 久草视频中文 | 久久综合色综合88 | 国内精品久久久久久中文字幕 | 国产精品 国内视频 | 麻豆视频免费在线 | av在线永久免费观看 | www.夜夜爱 | aav在线 | 夜夜操综合网 | 欧美国产精品久久久久久免费 | 国产视频中文字幕 | 国产一区视频在线观看免费 | 国产视频一区二区在线播放 | 美女视频国产 | 国产精品高潮呻吟久久av无 | 亚洲 欧美 变态 国产 另类 | 99视频免费播放 | 激情丁香| 久草网站在线观看 | 亚洲精品在线观看网站 | 久草在线资源观看 | 91大神免费在线观看 | 久久久久国产一区二区三区四区 | 亚洲 中文 欧美 日韩vr 在线 | 色噜噜狠狠狠狠色综合久不 | 久草在线视频免费资源观看 | 国产黄a三级三级 | 一区二区三区在线观看免费 | 久插视频| 国产色道 | 亚洲首页 | 亚洲国产成人久久综合 | 国产日韩在线观看一区 | 大型av综合网站 | h视频在线看 | 深爱激情综合网 | 日本视频精品 | 久久久久综合视频 | av电影免费观看 | av中文字幕第一页 | 区一区二在线 | av网址在线播放 | 中文字幕久久精品一区 | 黄色网中文字幕 | 欧美性一级观看 | 91在线最新| 国产黑丝一区二区 | 国产亚洲精品xxoo | 国内精品久久久 | 97精品免费视频 | 日韩欧美精品免费 | 日韩欧美视频在线播放 | 国产福利91精品一区 | 国产精品久久久久久久久久妇女 | 亚洲欧洲精品一区二区 | 日韩在线高清视频 | av免费高清观看 | 青青河边草免费观看完整版高清 | 亚洲综合丁香 | av高清网站在线观看 | 能在线观看的日韩av | 视频精品一区二区三区 | 国产一级免费观看 | 91成人免费 | 中文字幕一区二区三区四区久久 | 黄色软件网站在线观看 | 国产精品久久久久久爽爽爽 | 成在线播放 | 天天插天天爱 | 国内久久 | 国产精品扒开做爽爽的视频 | 久久久久久久久久免费视频 | av大片网站 | 一区二区三区日韩在线 | 国产精品久久久久毛片大屁完整版 | 久久久精品免费看 | 99精品欧美一区二区三区黑人哦 | 久久久久国产一区二区三区四区 | 日本爽妇网 | 99亚洲精品在线 | 国产精品美女 | 国产精品色婷婷 | 国产成人精品久久久久蜜臀 | 免费黄色在线播放 | av 在线观看| av在线播放观看 | 亚洲国产精品500在线观看 | 九九热视频在线免费观看 | 免费观看一区二区三区视频 | 九九一级片 | 人操人| 国产精品久久久久久久久久久免费 | 国产午夜精品一区二区三区在线观看 | 欧美日韩不卡一区 | 成人国产精品久久久春色 | 欧美性护士 | 日韩区视频 | 天天av综合网 | 国产成人精品一区二区三区网站观看 | 开心激情五月网 | 成人免费在线视频观看 | 国产欧美中文字幕 | 麻豆精品传媒视频 | 黄色影院在线免费观看 | 日韩超碰 | 免费看十八岁美女 | 国产日本在线 | 色中色综合| 99日韩精品 | 欧美精品久久99 | 五月天综合色激情 | 欧美ⅹxxxxxx | 91福利视频在线 | 玖玖视频国产 | 91在线免费视频 | 97人人人人 | 久草综合在线 | 91在线观看视频网站 | 国产成人精品综合久久久 | 99热精品久久 | 亚洲国产高清在线观看视频 | 日日夜夜草 | 亚洲精品午夜久久久久久久久久久 | 99九九免费视频 | 国产99久久九九精品免费 | 亚洲精品综合在线 | 国产一级视频在线观看 | 国产免费又粗又猛又爽 | 中文字幕国语官网在线视频 | 91av视频免费在线观看 | 91视频 - v11av | 久久久在线观看 | 久一在线| 狠狠干狠狠插 | 最新日韩视频在线观看 | 久久久久久久国产精品视频 | 久草在线综合 | 久草在线免费新视频 | 久久看片网 | 97网站| 安徽妇搡bbbb搡bbbb | 亚洲高清不卡av | 狠狠躁日日躁 | 欧美大片在线观看一区 | 伊人激情网 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 中文字幕一区二区三区乱码在线 | 亚洲天堂网视频在线观看 | 在线欧美最极品的av | 久草在线免费新视频 | 天天爽夜夜爽人人爽一区二区 | 日日操日日插 | 精品国产一区二区三区不卡 | 天天天天色射综合 | 99久久精品国产一区二区成人 | 久久精品小视频 | 日韩高清国产精品 | 91伊人久久大香线蕉蜜芽人口 | 久久久在线观看 | 亚洲综合色丁香婷婷六月图片 | 免费在线观看一级片 | 成人aaa毛片 | 日韩在线观看你懂的 | 婷婷色中文字幕 | 国产精品毛片一区二区 | 久久久久免费网站 | 国模精品在线 | 国产成人一级电影 | 欧美地下肉体性派对 | 国产亚洲精品无 | 欧美精品久久久久久久久老牛影院 | 福利网在线 | 国产又粗又硬又长又爽的视频 | 一本一本久久a久久精品牛牛影视 | 久草在线官网 | 亚洲爽爽网 | 日日日日干 | 久久综合九色综合久久久精品综合 | 日韩剧 | 中文字幕在线有码 | 成人在线你懂得 | 狠狠色丁香婷婷综合久久片 | 香蕉一区| 天天操天天操天天操天天操天天操 | 国产亚洲精品久 | 精品福利在线 | 久久久精品成人 | 国产一区二区播放 | 一区二区精品国产 | 午夜精品久久久久久久99热影院 | 日韩在线观看精品 | 伊人导航 | 97国产大学生情侣白嫩酒店 | 一区二区三区四区五区在线 | 亚洲精品视频观看 | 亚洲干视频在线观看 | 色偷偷av男人天堂 | 欧美日韩在线视频观看 | 99久久爱| 91精品啪在线观看国产 | 在线视频中文字幕一区 | 一区二区三区久久 | 亚洲欧美日韩国产一区二区三区 | 国产亚洲精品女人久久久久久 | 精品国产精品一区二区夜夜嗨 | 国产一区视频在线观看免费 | 黄色一级在线视频 | 国产精品国产三级国产aⅴ无密码 | 99精品免费在线观看 | 天天爽人人爽夜夜爽 | 国产精品区在线观看 | 天天拍天天干 | 亚洲欧洲中文日韩久久av乱码 | 日日碰狠狠添天天爽超碰97久久 | 一区二区视 | 日本爱爱免费视频 | 在线观看一级片 | 色综合久久久 | 久保带人| 精品产品国产在线不卡 | 久久精品网站视频 | 欧美大片www | 免费在线观看av网址 | 久久不射电影院 | 亚洲精品在线观 | 中文字幕在线播放av | 亚洲午夜久久久久久久久电影网 | 久在线| 日本久久久久久久久久 | 久久综合婷婷综合 | 午夜影院在线观看18 | 免费亚洲成人 | 91精品国产三级a在线观看 | 久久er99热精品一区二区三区 | 国产精品一区二区av日韩在线 | 国产高清一区二区 | 久久精品激情 | 日韩av网址在线 | 成人av一区二区兰花在线播放 | 免费a一级| 亚洲国产精品久久久 | 日韩精品视频在线观看免费 | 福利视频午夜 | 99re8这里有精品热视频免费 | 天天综合91 | 精品理论片 | 成人黄色小说视频 | 欧美日韩在线免费观看 | 成人中文字幕av | 九九综合九九 | 色福利网站 | 在线观看黄 | 亚洲人久久久 | 久久国产午夜精品理论片最新版本 | 日韩精品视 | 日本精品视频免费 | 色视频网址 | 中文字幕中文中文字幕 | 综合天天| 久久免费视频观看 | 久久久久久久久久久综合 | 成人a视频在线观看 | 国产精品久久久久久久久久久久 | 久草视频在 | 中文字幕在线网址 | 亚洲性视频 | 国产综合在线观看视频 | 91网页版免费观看 | 国产免费一区二区三区最新6 | 99色 | 久久精品精品 | 在线观看麻豆av | 97福利社| 日韩精品高清不卡 | 欧美精品日韩 |