摘要:的路由是使用了這個庫作者寫了一篇帖子介紹了它寫這個庫的原因原文鏈接使用正則的快速路由庫前段時間我在路由庫遇到了一些問題。它號稱比現用的路由庫快幾個數量級的,因為為了達到這個這個目的,這個庫是通過的擴展實現的。
首先先區分一下概念:
路由是指一個過程,就是利用定義好的一些規則,讓不同的URI能夠調用不同的處理器(一個匿名函數或者一個類中的方法)這樣一個過程。
平常很多框架所說的定義一個路由就是注冊一個這樣的規則到系統中去。
slim的路由是使用了FastRoute這個庫,作者寫了一篇帖子,介紹了它寫這個庫的原因(原文鏈接):
使用正則的快速路由庫前段時間,我在Pux路由庫遇到了一些問題。它號稱比現用的路由庫快幾個數量級的,因為為了達到這個這個目的,這個庫是通過PHP的C擴展實現的。
然而,粗略地看了Pux的源碼之后,我強烈懷疑這個庫優化了路由處理的錯誤部分,而我不借助于C擴展卻可以很輕松地到更好的性能。當我看了Pux的基準測試之后,發現只測試了幾個非常簡單實際的單路由例子,我就更加肯定了我的懷疑。
為了進一步調查這個問題,我寫了一個小型路由庫:FastRoute,這個庫實現了接下來描述的分發處理。為了給出預先觀點,我貼出了和Pux庫對比的小型基準測試結果:
1 placeholder | Pux (no ext) | Pux (ext) | FastRoute ----------------------------------------------------- First route | 0.17 s | 0.13 s | 0.14 s Last route | 2.51 s | 1.20 s | 0.49 s Unknown route | 2.34 s | 1.10 s | 0.34 s 9 placeholders | Pux (no ext) | Pux (ext) | FastRoute ----------------------------------------------------- First route | 0.22 s | 0.19 s | 0.20 s Last route | 2.65 s | 1.78 s | 0.59 s Unknown route | 2.50 s | 1.49 s | 0.40 s
這個基準測試使用了一百個路由,然后找出其中最快的路由(最佳例子),最慢的路由(最差的例子)和一個總共的未知平均路由。測試通過設置一個變量分為兩部分,一部分使用了1個占位符,另一部分使用了9個占位符。整個測試很明顯進行了循環了幾百次。
關于路由的問題為了確保我們在說同一個事物,讓我們定義一下"路由"是什么。在大多數實際的形式中,它是指跟以下形式類似的利用一套路由定義:
$r->addRoute("GET", "/user/{name}/{id:d+}", "handler0"); $r->addRoute("GET", "/user/{id:d+}", "handler1"); $r->addRoute("GET", "/user/{name}", "handler2");
然后調度處理一個基于它們的URI的過程:
$d->dispatch("GET", "/user/nikic/42"); // => provides "handler0" and ["name" => "nikic", "id" => "42"]
把這個過程提升到一個更抽象的層次,我們將會為路由定義提供HTTP方法和任何特定的格式。在本文中,我會考慮的唯一一樣事情是路由的調度階段——路由如何被解析或調度器生成的數據不會被覆蓋。
那么,路由處理的最耗時的部分是哪里呢?在一個混亂不堪的,過度被設計的系統中,它可能是實例化數十個對象和調用數百個方法的開銷。Pux庫在減少這方面的開銷做的很好。然而,在一個比較原始層次的系統中,依次經過一系列數十個或者數百個路由表達式,之后再通過提供的URI和它們進行匹配這個過程是最耗時的部分。讓這個過程更快就是本文的主題。
合并的正則表達式:
優化這類問題的最基本方法是避免一個個的去匹配那些正則表達式,相反地,把它們結合在一起,變成一個大的正則表達式,這樣的話,你只需要進行一次匹配就可以了。就拿最后一個例子的路由作說明,合并的正則表達式是這樣的:
Individual regexes: ~^/user/([^/]+)/(d+)$~ ~^/user/(d+)$~ ~^/user/([^/]+)$~ Combined regex: ~^(?: /user/([^/]+)/(d+) | /user/(d+) | /user/([^/]+) )$~x
這個轉化很簡單:基本上你只要將那些正則表達式一個個地用OR 連接在一起就可以了。當匹配這個合并的正則表達式,如何找出具體哪個路由規則被匹配了呢?為了找出來,讓我們來看一看preg_match這個函數對一個樣本的輸出:
preg_match($regex, "/user/nikic", $matches); => [ "/user/nikic", # full match "", "", # groups from first route (empty) "", # groups from second route (empty) "nikic", # groups from third route (used!) ]
那么,在$matches數組中找到第一個非空入口就是訣竅了(當然,沒算上第一個完全匹配)。
這里我貼上代碼,方便大家測試:
$regex = "~^(?:/user/([^/]+)/(d+)|/user/(d+)|/user/([^/]+))$~x"; preg_match($regex, "/user/nikic", $matches); var_dump($matches);
(?:regexp) 匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以后使用。這在使用 "或" 字符 (|) 來組合一個模式的各個部分是很有用。例如, "industr(?:y|ies) 就是一個比 "industry|industries" 更簡略的表達式。介紹
為了使用這個結果,你將需要一個額外的數據結構來映射$matches的索引到匹配的路由規則(或者,一些關聯那個路由規則的信息)
[ 1 => ["handler0", ["name", "id"]], 3 => ["handler1", ["id"]], 4 => ["handler2", ["name"]], ]
這里是一個實現整個處理流程的例子:
public function dispatch($uri) { if (!preg_match($this->regex, $uri, $matches)) { return [self::NOT_FOUND]; } // find first non-empty match (skipping full match) for ($i = 1; "" === $matches[$i]; ++$i); list($handler, $varNames) = $this->routeData[$i]; $vars = []; foreach ($varNames as $varName) { $vars[$varName] = $matches[$i++]; } return [self::FOUND, $handler, $vars]; }
在找到第一個非空的索引,關聯的數據就可以被查找到了。通過遍歷$matches數組并配對值和變量名,占位符的變量就可以被填充了。
那么這個方法執行起來效率如何呢?這里給出了和Pux的比較結果(使用C擴展):
1 placeholder | Pux (ext) | GPB-NC ----------------------------------- First route | 0.13 s | 0.20 s Last route | 1.20 s | 0.70 s Unknown route | 1.10 s | 0.16 s 9 placeholders | Pux (ext) | GPB-NC ----------------------------------- First route | 0.19 s | 0.41 s Last route | 1.78 s | 4.09 s Unknown route | 1.49 s | 0.30 s
GPB-NC表示“Group position based, non-chunked”調度。如你所見的那樣,在單個占位符的測試例子這個方法提供了不錯的性能。當然它不能打敗在最快路由上正確匹配的C擴展實現,但是在最糟糕匹配上,它表現地快了一點,如果一個都沒匹配的話,更快了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/23212.html
摘要:一團隊組織網站說明騰訊團隊騰訊前端團隊,代表作品,致力于前端技術的研究騰訊社交用戶體驗設計,簡稱,騰訊設計團隊網站騰訊用戶研究與體驗設計部百度前端研發部出品淘寶前端團隊用技術為體驗提供無限可能凹凸實驗室京東用戶體驗設計部出品奇舞團奇虎旗下前 一、團隊組織 網站 說明 騰訊 AlloyTeam 團隊 騰訊Web前端團隊,代表作品WebQQ,致力于前端技術的研究 ISUX 騰...
摘要:一團隊組織網站說明騰訊團隊騰訊前端團隊,代表作品,致力于前端技術的研究騰訊社交用戶體驗設計,簡稱,騰訊設計團隊網站騰訊用戶研究與體驗設計部百度前端研發部出品淘寶前端團隊用技術為體驗提供無限可能凹凸實驗室京東用戶體驗設計部出品奇舞團奇虎旗下前 一、團隊組織 網站 說明 騰訊 AlloyTeam 團隊 騰訊Web前端團隊,代表作品WebQQ,致力于前端技術的研究 ISUX 騰...
摘要:一團隊組織網站說明騰訊團隊騰訊前端團隊,代表作品,致力于前端技術的研究騰訊社交用戶體驗設計,簡稱,騰訊設計團隊網站騰訊用戶研究與體驗設計部百度前端研發部出品淘寶前端團隊用技術為體驗提供無限可能凹凸實驗室京東用戶體驗設計部出品奇舞團奇虎旗下前 一、團隊組織 網站 說明 騰訊 AlloyTeam 團隊 騰訊Web前端團隊,代表作品WebQQ,致力于前端技術的研究 ISUX 騰...
摘要:主進程的目的是為了讀取和評估配置并保持工作進程。默認情況下,這個配置文件名為。如果一個塊指令在大括號中有其他的指令,則稱之為上下文如和。放在配置文件最外面的指令的稱之為主文,指令在主文中在中,在中。注意指令已經被放置在環境中。 原文鏈接:http://nginx.org/en/docs/begi...轉自我的github有些地方覺得翻譯的不是很合理,所以在括號中寫出了原句。如果有地方翻...
摘要:主進程的目的是為了讀取和評估配置并保持工作進程。默認情況下,這個配置文件名為。如果一個塊指令在大括號中有其他的指令,則稱之為上下文如和。放在配置文件最外面的指令的稱之為主文,指令在主文中在中,在中。注意指令已經被放置在環境中。 原文鏈接:http://nginx.org/en/docs/begi...轉自我的github有些地方覺得翻譯的不是很合理,所以在括號中寫出了原句。如果有地方翻...
閱讀 1213·2021-11-25 09:43
閱讀 1969·2021-11-11 10:58
閱讀 1187·2021-11-08 13:18
閱讀 2659·2019-08-29 16:25
閱讀 3509·2019-08-29 12:51
閱讀 3307·2019-08-29 12:30
閱讀 748·2019-08-26 13:24
閱讀 3683·2019-08-26 10:38