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

          正則表達式是如何讓你的網(wǎng)頁卡住的(優(yōu)化100ms到2ms)

          共 5575字,需瀏覽 12分鐘

           ·

          2020-12-11 17:01

          授權轉載自:hjava

          https://segmentfault.com/a/1190000038320217

          概述

          正則表達式在我們?nèi)粘痰墓ぷ黜椖恐?,應該是一個經(jīng)常用到的技能。在做一些字符的匹配和處理的過程中,發(fā)揮了很大的作用。我們這篇文章主要是通過一個我在工作中遇到的性能問題,來探究下正則表達式是如何影響我們的代碼性能的。在我們遇到了正則表達式有性能平靜的時候,我們應該如何的來對它進行優(yōu)化?

          如果對正則表達式還沒有什么概念,或者說不了解的同學,可以先參考我之前寫過的博客:

          • 正則表達式語法入門:https://segmentfault.com/a/1190000015747364
          • 正則表達式高級進階:https://segmentfault.com/a/1190000015747441

          問題現(xiàn)狀

          在我們?nèi)粘5墓ぷ髦?,如果不需要去調(diào)整正則表達式的話,大部分人其實是會選擇性忽略它的。這就導致了大部分人對正則表達式其實并不是太了解。在正則表達式出現(xiàn)問題以后也不知道如何去解決。

          因為我在美團是負責做大象?Web/PC?的相關開發(fā),所以在日常的工作中免不了要經(jīng)常和正則表達式打交道,比如識別文本消息中的URL進行高亮,或者說識別會議室、解析特定格式展示不同的?UI?等。在這種情況下,我免不了會跟大量的正則表達式打交道。從長時間與正則打交道的經(jīng)歷中,也有了部分的經(jīng)驗總結。

          下面我們通過一個工作中具體的例子,來看下正則表達式是如何讓你的網(wǎng)頁卡住的?

          在最近的性能問題優(yōu)化排查中,我們發(fā)現(xiàn)在遇到文字內(nèi)容較多(約?15000?字)的文本消息文字處理時,?render?函數(shù)會有一個比較大的性能損耗,每次渲染需要差不多?100ms?。因為消息每次渲染都是?20?條一起,因此正則表達式一旦有性能問題,就會因為多次渲染的放大效應,被用戶很明顯的感知到。如果每條消息處理都需要?100ms?,那么?20?條消息處理就會直接卡頓?2s?,這其實對于用戶來說是不可以接受的。

          具體我們可以看下火焰圖(火焰圖就是?Chrome?的?devtools?中,分析?profile?時候的圖表,大家可以理解為一個調(diào)用時間圖譜,如果不了解,推薦看看阮一峰老師的如何讀懂火焰圖?- 阮一峰的網(wǎng)絡日志 :https://www.ruanyifeng.com/blog/2017/09/flame-graph.html):

          通過上述的火焰圖,我們可以看到這個?render?渲染函數(shù)每次執(zhí)行都差不多?100ms?。對于?JavaScript?來說,?100ms?其實時間已經(jīng)很長了。那么這一百毫秒中具體干了哪些事情呢?

          我們簡單的梳理一下當前的代碼,發(fā)現(xiàn)最有可能的原因就是正則耗時的影響。在消息處理中,有兩個需要進行匹配的正則,一個是匹配會議室進行高亮的,一個是匹配引用消息進行格式轉換的。這兩個正則分別如下:

          const?QUOTED_MSG_REG?=?/([^「]*?)「((?:[a-zA-Z0-9\u4E00-\u9FBF_\.\s]{0,40})\:(?:.|\n)*)」\n(—){10}\n((?:\S|\s)*)$/m;

          const?MEETING_ROOM_REG?=?/北京廳|天津廳|石家莊廳|濟南廳|哈爾濱廳|...(此處省略200+個會議室)|臺灣廳/mg;

          這個兩個正則表達式用來匹配的文本如下:

          //?引用格式
          「張三:老司機」
          ——————————
          帶帶我

          //?會議室
          張三呀,我們?nèi)?常德廳?開個會吧,叫上其他人

          一開始看,大家可能覺得這兩個正則都很正常,我們在正常的工作中也會寫出這樣的正則表達式,沒有發(fā)現(xiàn)什么問題。

          如果告訴你這兩個正則表達式執(zhí)行有性能問題,那么大家可能還會覺得,會議室匹配的文本正則這么長,需要匹配的會議室這么多,肯定是這個正則有性能問題,導致了執(zhí)行時間過長。

          那么具體情況到底是不是和我們直觀感受一樣呢?我們來對具體問題進行一個分析。

          問題分析

          為了分析我們上面說到的這兩個正則表達式性能到底怎么樣,我從網(wǎng)上找了一些文字,來模擬消息的內(nèi)容。通過使用正則表達式進行匹配,在Node端執(zhí)行計算耗時,得到的一個字數(shù)與時間的關系圖如下,表格的橫坐標是字數(shù),縱坐標是時間(?ms?):

          這個和大家的猜測是不是一樣?在我之前最早的猜測中,我也以為是正則長度越長,那么性能就越差。但是,這個和我的猜測正好相反,反倒是看上去比較短的。引用正在表達式性能問題最大。

          從我們分析的數(shù)據(jù)來看,在?10000?字之前,其實差別沒有那么大。但是在超過?10,000?個字的時候,其實耗時差異就比較明顯了。

          大家可以看到引用的這個正則表達式,他的耗時其實是發(fā)生了指數(shù)型的上升。在超過?50,000字,以后其實這個正則你可以認為基本上就不能夠再使用了,而且這還是在性能比較好的?MacBook?情況下。如果是在一些更老的電腦,或者說?Windows?的低端本上,那么這個耗時其實還會更大。你想想你,你能夠接受你的開發(fā)的項目,卡住?2?秒不動嗎?

          反倒是我們覺得比較復雜的這個會議室正則表達式,它在匹配的內(nèi)容字數(shù)增加的情況下,性能其實沒有明顯的增加,一直都穩(wěn)定在?100?毫秒以下。

          看到這里,有人可能會覺得是不是?match?方法,它比較吃性能呢?也有人可能會想,我們是不是在?match?之前增加一個相同正則表達式的test判斷?如果符合的話,我們再執(zhí)行?match,這樣是不是就能夠提高我們的性能呢?

          那么我們把?match?方法換成?test?方法來看一下,這樣能不能夠提升我們正則匹配的性能呢?下圖是我們使用會議室正則表達式來進行匹配的一個耗時圖。我們從圖中可以看到相關的執(zhí)行耗時情況:

          從圖中可以看到,?test?方法并不會比?match?方法節(jié)省更多的時間,相反來看他的耗時其實比?match?還略微有增加。不過可能就是幾個毫秒。我嘗試了一下性能問題更明顯的引用正則表達式,得到了結論也是一樣的。所以我們想到的先使用?test?方法來進行判斷,如果?test方法命中的話再進行?match?。這個不但沒有優(yōu)化,反倒是可能會損耗雙倍的性能。

          既然相同的正則表達式使用任意一個方法執(zhí)行的時候都會有比較明顯的性能問題,那么我們就只能從正則表達式本身的優(yōu)化入手了。我們來看一下,為什么我們覺得比較復雜的正則表達式,耗時沒有什么變化。反而我們認為比較簡單的正則表達式時間的增長卻這么明顯呢?

          原理分析

          其實,正則表達式性能最大的影響來自于正則表達式的回溯。如果一個正則表達式回溯的越多,那么它的性能損耗就越明顯。我們可以去看一下上面兩個正則表達式的情況。

          其實上面兩個正則表達式都有回溯的問題。如果大家不了解,回溯,可以去看下我之前的那一篇 正則表達式高級進階。在這里我們簡單介紹一下回溯回溯的原因:正則表達式在匹配的過程中需要往回走重新進行匹配,這就會導致回溯。一般產(chǎn)生回溯的有這么幾種情況,一種是分支,一種是量詞。

          我們可以看看上面兩個正則表達式,會議是這個正則比較簡單,他其實是很多分支的集合體;引用的這個正則就不同了,他的回溯主要是來源于量詞。尤其是[^「]*這種的存在,導致了大量的回溯情況。

          所以說一個正則表達式性能好不好跟他的長短沒有必然的聯(lián)系。而是跟他具體的寫法有關。如果這個正則表達式很多地方都有回溯的情況,那么他的性能必然就好不了。反過來說,如果一個正則表達式雖然很長很復雜,但是它能夠盡可能的避免回溯。需要匹配的文本也盡可能的清晰,那么這種情況下它的性能其實是很不錯的。

          解決方案

          遇到這個問題,我們一般會有以下兩個解決方案。

          優(yōu)化正則表達式本身

          第一個解決方案就是盡可能的去優(yōu)化這個正則表達式本身,去盡可能消除里面一些回溯的情況。這個也是我們一般最常用的一個解決方案。具體有以下2個操作:

          1. 在明確匹配規(guī)則的情況下,使用?\d{1, 30}?來替換?.*?,盡可能的去明確我們需要匹配的類型與長度。
          2. 在需要進行不明確數(shù)量匹配的時候,盡可能的使用非貪婪匹配,而不是使用貪婪匹配。

          同時,還有個規(guī)則:在不需要捕獲組的情況下,括號盡可能的使用非捕獲組(與回溯無)。

          總體上來說就是:如果一個正則表達式越精確,捕獲的元素越少,那么它的性能就會越好。反之,如果有大量的模糊匹配跟回溯的情況,那么它的性能大概率就不怎么好。

          在一般的場景中,我們使用了這個方法,基本上我們的性能問題就能夠迎刃而解了。

          但是,那么如果我們繼續(xù)要匹配比較復雜的正則,同時這個正則又沒有辦法避免回溯的情況,我們應該怎么去優(yōu)化這個性能的?

          優(yōu)化正則表達式匹配順序

          也就是說在這種情況下,這個正則表達式其實是沒有辦法再進行優(yōu)化了,但是我們又需要在日常的項目中使用,不能直接廢棄。這就需要我們使用另外的優(yōu)化方案了。

          在正則沒有辦法修改的情況下,我們可以做正則匹配的分級,盡可能使用一些性能比較高的正則表達式,先進行一些過濾匹配。在命中我們需要匹配的條件以后,再使用比較復雜的正則表達式進行匹配。從而避免復雜的正則表達式頻繁的被調(diào)用。

          我舉一個簡單的例子,還是以上面的引用正則表達式來分析。如果這個正則表達式我沒有辦法再進行進一步優(yōu)化了情況下,我們可以先把他的一些特定的規(guī)則摘取出來,進行一個前置校驗。我們可以簡單的來看一下下面一個代碼示例:

          let?str?=?'xxxxxx';?//長文本

          const?LINE_REG?=?/\n(—){10}\n/m;
          const?QUOTED_MSG_REG?=?/([^「]*?)「((?:[a-zA-Z0-9\u4E00-\u9FBF_\.\s]{0,40})\:(?:.|\n)*)」\n(—){10}\n((?:\S|\s)*)$/m;

          if(LINE_GER.test(str))?{
          ????let?result?=?str.match(QUOTED_MSG_REG);
          ????//?do?something
          }

          不要在主線程中執(zhí)行

          如果一個正則表達式?jīng)]有辦法通過上述兩種方案進行優(yōu)化(這個概率其實已經(jīng)很低了,感覺和彩票中獎差不多了),那么我們還有一個最終的解決方案,就是使用?Web Workder?,來進行耗時的操作計算。

          這樣的話,我們至少在主線程執(zhí)行過程中,不會有卡住影響用戶操作的問題。

          不過,在這個方案中,需要考慮到大量數(shù)據(jù)通過?postMessage?傳遞到?Web Worker?中的性能損耗問題。

          這個方案本質(zhì)上比較簡單,我在具體項目中也沒有使用到,因此不展開講了,有興趣了解的同學可以自行上網(wǎng)查閱相關資料,或者評論私信留言討論。

          從上面的代碼中我們可以看到,我們可以選取一個沒有回溯的明確特征條件來先進行一次快速的匹配。一般情況來說沒有回溯的正則匹配效率都是特別高,即使是在大量文本處理的情況下也不會對性能有什么太大的影響。在進行了第一次正則表達式匹配后,如果這個文本還是符合當前的條件,那么說明有較大概率它其實是需要我們命中的,那么我們再執(zhí)行正則匹配即可。

          這樣的話,我們就能夠避免大部分的無意義的性能消耗。

          服務端數(shù)據(jù)處理

          如果一個數(shù)據(jù)量太過龐大(超過1M的文本)時,我推薦對數(shù)據(jù)進行分頁,不要一次性處理所有數(shù)據(jù)(這個時候正則已經(jīng)不是瓶頸了,JS執(zhí)行引擎才是瓶頸)。

          但是,有些神奇的項目就是會有這種訴求,遇到這種情況時,我們必須(不是可以,是必須)借助服務端來進行數(shù)據(jù)處理,前端只做簡單的展示邏輯(即使是展示這么大量的數(shù)據(jù),渲染也會有比較明顯的卡頓和耗時)。

          如果沒有后端的支持,那么自己用Node搭建一個簡單的中轉處理服務都行。這個時候需要關注的,就是自己的Node服務如何能夠彈性擴容了。

          效果驗證

          在我的項目遇到的性能問題中,只使用了前兩個方案對引用的正則表達式進行了優(yōu)化。我們可以來看一下優(yōu)化后的渲染耗時情況:

          在通過對正則表達式進行優(yōu)化后,我們的每次文本渲染時間從?100ms?直接降到了不到?2ms。這可是?50?倍的性能提升。對于?15000?字的文本來說,這個速度可以算是沒有任何的性能影響了。

          我們還試了試極限情況下?1000000?字的情況,渲染也能夠控制在?20ms?以內(nèi),這和之前相比,進步還是很明顯的。

          總結

          正則表達式在我們的日常代碼使用中其是很常見的。但是稍有不慎我們就會遇到性能問題。大部分在寫代碼的過程中,不會去考慮這個正則表達式性能怎么樣,都會下意識覺得反正處理的文本長度不大,寫的再差也沒有什么影響。但是,在項目逐漸發(fā)展過程中,有可能由于產(chǎn)品策略調(diào)整或者數(shù)據(jù)的積累,某一個不起眼的正則表達式,就會對整個項目的性能產(chǎn)生決定性影響。

          因此我們在具體開發(fā)的過程中一定要有性能的意識,我們寫的任意一個正則表達式都有可能會導致整個系統(tǒng)的性能問題。因此我們寫的每一個正則表達式都應該盡可能的準確,盡可能的減少執(zhí)行次數(shù)。

          再遇到正則的性能問題時,正則表達式的優(yōu)化手段主要有3個:

          1. 我們需要盡可能的去讓我們的正則表達式準確化,越準確的正則表達式匹配時,他的回溯情況就越少,所以它的性能就越高。
          2. 在正則表達式已經(jīng)沒有辦法再進行優(yōu)化的情況下,我們可以先選取一些沒有回復情況的特征值進行先置條件判斷,這樣的話,我們能夠盡量多的去避免一些無意義的好事匹配,優(yōu)化我們的性能。
          3. 借助其他線程或者服務來進行正則處理,避免用戶卡頓。

          希望能夠通過上述的具體實戰(zhàn)優(yōu)化,能夠讓大家了解正則表達式在項目中對性能的影響,也歡迎大家在遇到正則表達式相關的問題時,隨時討論交流,大家一起解決問題,一起進步。


          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  中文字幕毛片 | 20011年高清a免费看一级毛片 | 国产日韩+欧美在线观看 | 在线视频三区 | 天天操天天操天天操天天操 |