摘要:而且需要在特定的菜單位置上顯示待辦事項(xiàng)的數(shù)量。以我的博客某篇文章加載為例最右邊有個(gè)紅框標(biāo)識(shí)的就是每條資源的加載耗時(shí),我們可以看到第一條是服務(wù)端的處理速度。接下來我們就可以直接去看代碼了。在大腦中構(gòu)思了一下,其實(shí)這些完全可以通過遞歸來實(shí)現(xiàn)嘛。
原文是在我自己博客中,小伙伴也可以點(diǎn)閱讀原文進(jìn)行跳轉(zhuǎn)查看,還有好聽的背景音樂噢背景音樂已取消~ 2333333
大爺我就算功能重做,模塊重構(gòu),我也不做優(yōu)化?。?!運(yùn)行真快!
本文主要探討的核心是【為什么不要在循環(huán)中使用數(shù)據(jù)庫(kù)操作?】
用了一個(gè)例子來說明為什么不要這樣做的原因以及當(dāng)遵循了這條規(guī)則后,所帶來的好處:代碼運(yùn)行效率的提升、心情好(亂入-_-)之類的。
最近在對(duì)一個(gè)老項(xiàng)目進(jìn)行維護(hù)的時(shí)候,發(fā)現(xiàn)有一個(gè)頁(yè)面加載很耗時(shí),響應(yīng)速度在1.7s以上,而且這個(gè)頁(yè)面粗略看起來需要加載的東西也不是很多,為什么加載會(huì)這么慢呢?本著一探究竟和對(duì)這些慢響應(yīng)無法忍受的態(tài)度去看了一下,發(fā)現(xiàn)它的代碼寫的很糟糕,到處都是循環(huán),而且還在循環(huán)中進(jìn)行了sql查詢。后來在自己的優(yōu)化下,從均加載1.5s到均0.02s,實(shí)現(xiàn)了一個(gè)質(zhì)的飛躍。
本文,就是總結(jié)一下,自己在遇到這種代碼的處理方式,以及思想的演化
本文所要優(yōu)化的是一段,由權(quán)限控制的菜單,共有兩級(jí)。而且需要在特定的菜單位置上顯示待辦事項(xiàng)的數(shù)量。普普通通的一段權(quán)限控制菜單訪問的功能,其實(shí)處理起來也就是多了一個(gè)【特定菜單位置上顯示代辦數(shù)量】的功能,簡(jiǎn)單思考一下,只要找到對(duì)應(yīng)的菜單id,在其上面增加一個(gè)對(duì)應(yīng)的數(shù)字就可以了。想是這么想,做起來呢?
確定問題所在遇到網(wǎng)頁(yè)加載很慢的時(shí)候,首先要確定到底是哪一部分加載很慢??梢酝ㄟ^瀏覽器f12打開調(diào)試工具,在network選項(xiàng)里,查看當(dāng)前頁(yè)面上每條資源的加載耗時(shí)情況來推斷。以我的博客某篇文章加載為例:
最右邊有個(gè)紅框標(biāo)識(shí)的就是每條資源的加載耗時(shí),我們可以看到第一條是php服務(wù)端的處理速度。下面的便是各種資源了。我要優(yōu)化的那段業(yè)務(wù)中,發(fā)現(xiàn)正是由php服務(wù)端處理加載過慢帶來的巨大耗時(shí),平均每次這里加載需要1.5s以上。其他資源的加載速度平均都是在幾十ms,那么就可以確定是這段php寫的有問題了。
接下來我們就可以直接去看php代碼了。
優(yōu)化 檢查代碼,理解代碼找到對(duì)應(yīng)的代碼塊,測(cè)試了一下這段代碼塊的處理時(shí)間,發(fā)現(xiàn)用時(shí)1.5s之多,有點(diǎn)震驚。簡(jiǎn)單看了一下代碼,兩大段過百行的代碼塊,經(jīng)過一段時(shí)間的分析,發(fā)現(xiàn)有很多重復(fù)的、不必要的地方,現(xiàn)整理代碼邏輯(偽代碼)如下:
$value1) { /** * 2、取出二級(jí)菜單 并循環(huán)二級(jí)菜單 */ foreach ($second_menu as $key2 => $value2) { /** * 3、取出三級(jí)菜單 循環(huán)三級(jí)菜單 當(dāng)前菜單項(xiàng)含有url信息 * 4、對(duì)權(quán)限進(jìn)行驗(yàn)證 判斷當(dāng)前主菜單下是否擁有可以訪問的權(quán)限 * 5、對(duì)頂級(jí)菜單需要顯示的待辦事項(xiàng)做處理 */ foreach ($third_menu as $key3 => $value3) { // 權(quán)限驗(yàn)證 $flag = $this->auth->check($ctrl, $action); /** * 做處理 在頂級(jí)菜單上增加待辦事項(xiàng)數(shù) * to do something */ // ............ // ............ /** * 這里奇葩的是又調(diào)用了另外一個(gè)方法 * 傳遞了一個(gè)top_id 一級(jí)菜單ID * 然后根據(jù)一級(jí)菜單重復(fù)2、3在對(duì)應(yīng)的三級(jí)菜單上再增加待辦事項(xiàng) */ $this->handle_son_backlog($top_id, $backlog_data); } } }
這段代碼塊都做了什么呢?文字簡(jiǎn)述如下:
取出一級(jí)菜單
循環(huán)一級(jí)菜單,根據(jù)一級(jí)菜單id,取出二級(jí)菜單
循環(huán)二級(jí)菜單,根據(jù)二級(jí)菜單id,取出三級(jí)菜單,三級(jí)菜單包含url信息
循環(huán)三級(jí)菜單,驗(yàn)證權(quán)限,并決定一級(jí)菜單是否顯示:將url拆分成uri塊,生成驗(yàn)證權(quán)限所需要的參數(shù)ctrl(控制器)和action(方法)
根據(jù)確定好的一級(jí)菜單,增加一級(jí)菜單需要顯示的待辦事項(xiàng)數(shù)
好了,以上就是第一個(gè)函數(shù)的作用,然而,這還沒完,在循環(huán)三級(jí)菜單的時(shí)候,又調(diào)用了另外一個(gè)方法handle_son_backlog(),這個(gè)方法傳了兩個(gè)參數(shù),一個(gè)是一級(jí)菜單id,另外一個(gè)是待辦事項(xiàng)數(shù)組,那么這個(gè)方法又做了什么呢?
根據(jù)一級(jí)菜單id,取出二級(jí)菜單
循環(huán)二級(jí)菜單,取出三級(jí)菜單
菜單權(quán)限驗(yàn)證
在對(duì)應(yīng)的三級(jí)菜單上增加待辦事項(xiàng)數(shù)
理解完原來代碼的用意后,再修改起來就不難。本來打算再原本的基礎(chǔ)上修改,但是用了一段時(shí)間發(fā)現(xiàn),代碼寫得太亂,根本沒辦法在看,于是我決定,自己寫,先改造一部分,去掉多余的第二個(gè)函數(shù)
第一次嘗試修改改變代碼塊的可讀性;
經(jīng)過第一次想法的修改之后,去掉了第二個(gè)方法多余的循環(huán)、重復(fù)驗(yàn)證的問題,代碼變得稍微精簡(jiǎn)一些了:
/** * 對(duì)特定的菜單進(jìn)行處理 增加待辦事項(xiàng) * @param array &$son_data 子菜單信息 * @param array $backlog_data 待辦事項(xiàng)數(shù)據(jù) * @return array */ function handle_son_backlog(array &$son_data, array $backlog_data) { if (empty($son_data["id"])) { return false; } switch ($son_data["id"]) { case "": $son_data["backlog_num"] = (isset($backlog_data["xxx"]) && empty($backlog_data["xxx"])) ? $backlog_data["xxx"]: ""; break; default: # code... break; } return $son_data; } /** * 獲取菜單 * @param array $backlog_data 待辦事項(xiàng)數(shù)據(jù) * @return array */ function get_menu() { /** * 1、取出一級(jí)菜單 并循環(huán)一級(jí)菜單 */ foreach ($top_menu as $key1 => $value1) { /** * 2、取出二級(jí)菜單 并循環(huán)二級(jí)菜單 */ foreach ($second_menu as $key2 => $value2) { /** * 3、取出三級(jí)菜單 循環(huán)三級(jí)菜單 當(dāng)前菜單項(xiàng)含有url信息 * 4、對(duì)權(quán)限進(jìn)行驗(yàn)證 判斷當(dāng)前主菜單下是否擁有可以訪問的權(quán)限 * 5、對(duì)頂級(jí)菜單需要顯示的待辦事項(xiàng)做處理 */ foreach ($third_menu as $key3 => $value3) { // 權(quán)限驗(yàn)證 $flag = $this->auth->check($ctrl, $action); /** * 做處理 在頂級(jí)菜單上增加待辦事項(xiàng)數(shù) * to do something */ /** * 對(duì)子菜單的待辦事項(xiàng)做處理 */ $this->handle_son_backlog($value3, $backlog_data); } } } }
修改好之后,運(yùn)行0.6s,快了一倍,但是這肯定是不夠的。還是慢?。。?/p> 還能不能再快?
使用遞歸結(jié)構(gòu);
略看第一次修改后的代碼還是有可以提速的地方。三層循環(huán)寫的著實(shí)讓人辣眼睛啊,因?yàn)樵谘h(huán)中還有數(shù)據(jù)庫(kù)操作,請(qǐng)注意:任何在循環(huán)中參與數(shù)據(jù)庫(kù)的處理都是不明智的選擇。在大腦中構(gòu)思了一下,其實(shí)這些完全可以通過遞歸來實(shí)現(xiàn)嘛。只需要把菜單一股腦取出來,在用遞歸形成樹形結(jié)構(gòu)就可以了。說干就干
先說說我這段處理大致思路:
取出菜單表里所有的菜單數(shù)據(jù)
調(diào)用遞歸方法,形成樹形結(jié)構(gòu)
遞歸的方法中,做一些特殊處理
確定是第三層菜單
對(duì)第三層菜單做權(quán)限處理
對(duì)第三層菜單做待辦事項(xiàng)處理
差不多就是如上幾步思路,完成版?zhèn)未a如下:
/** * 對(duì)菜單進(jìn)行遞歸處理 并驗(yàn)證權(quán)限 增加待辦事項(xiàng)數(shù)量 * @param array &$menu 菜單 * @param array $backlog_data 待辦事項(xiàng)數(shù)據(jù) * @param array $menu_list 原來的菜單 * @param int $pid pid * @param int|integer $last_pid 父菜單id * @param int|integer $i 遞歸標(biāo)識(shí)(用于執(zhí)行特定操作) */ function get_handle(array &$menu, array $backlog_data, array $menu_list, int $pid, int $last_pid = 0, int $i = 0) { foreach ($menu_list as $key => $value) { if ($value["pid"] == $pid) { if ($i == 1) { // 要驗(yàn)證的url $check_url = explode("?", $value["url"]); // 拆分成uri數(shù)據(jù)段 $check_url_arr = explode("/", $check_url[0]); // 控制器名 $ctrl = $check_url_arr[0] . "_" . $check_url_arr[1]; // 方法名 $action = isset($check_url_arr[2]) ? $check_url_arr[2] : "index"; if ($this->auth->check($ctrl, $action)) { $menu[$last_pid]["zi"][$value["type_id"]] = $this->handle_son_backlog($value, $backlog_data); } } else { $this->get_handle($menu, $rule_list, $backlog_data, $menu_list, $value["type_id"], $pid, 1); } } } } /** * 獲取菜單 * @param array $backlog_data 待辦事項(xiàng)數(shù)據(jù) * @return array */ function get_menu(array $backlog_data) { // 獲取菜單列表 $menuList = $menuModel->get_list(["id", "name", "pid", "url"], ["version" => 1]); // 取得一級(jí)菜單 foreach ($menuList as $key => $info) { if ($info["pid"] == 0) { $menu[$info["id"]] = $info; } } foreach ($menu as $id => $info) { // 對(duì)菜單作遞歸處理 $this->get_handle($menu, $backlog_data, $menuList, $info["id"]); /** * 判斷當(dāng)前主菜單下是否有子菜單 如果沒有則釋放掉當(dāng)前一級(jí)菜單 * 如果有則對(duì)當(dāng)前一級(jí)菜單進(jìn)行待辦事項(xiàng)處理 */ // // // } return $menu; }
差不多了就來進(jìn)行調(diào)試一下吧,運(yùn)行一看0.3s,感覺跟第一次修改的時(shí)候運(yùn)行的也差不多嘛?。ㄟ@時(shí)候已經(jīng)比最初的運(yùn)行速度提升了差不多4倍。)但隱隱覺得這還不夠...
還能不能更快?減少數(shù)據(jù)庫(kù)查詢次數(shù);
重新梳理一下代碼邏輯,試圖找到可以優(yōu)化的點(diǎn)。在梳理的時(shí)候注意到一個(gè)地方,就是$this->auth->check()這個(gè)檢查權(quán)限的方法了。去跳轉(zhuǎn)查看了一下,發(fā)現(xiàn)這方法也是查一次查一下數(shù)據(jù)庫(kù),這樣的話,綜合起來,這里還是牽涉到在循環(huán)中查詢數(shù)據(jù)庫(kù)的操作了。這塊必須優(yōu)化。
如果把當(dāng)前登陸者已擁有的全部權(quán)限都取出來,替換掉check()這一塊,是不是效率就會(huì)更快些?感覺答案應(yīng)該是肯定的!
在經(jīng)過一些調(diào)整之后,發(fā)現(xiàn)程序執(zhí)行的速度有了極大的提升,增加了一段取出所有權(quán)限的操作:
/** * 獲取用戶所有權(quán)限列表 * @param int $user_id 用戶id * @return array/boolean */ function get_user_operation_list(int $user_id) { $group_ids = $this->get_value_by_pk($user_id, "groupid"); if ($group_ids) { $group_ids_arr = explode(",", $group_ids); // 取出用戶所擁有的權(quán)限 控制器和方法名 $result = $this->db->select("o.module, o.action") ->from("admin_group_operations ago") ->join("operations o", "ago.operations_id = o.operation_id", "left") ->where_in("ago.group_id", $group_ids_arr) ->where("o.operation_id >", 0) ->get() ->result_array(); if (!empty($result)) { $new_data = []; // 生成指定的鍵值對(duì) foreach ($result as $key => $value) { $new_data[] = $value["module"] . "/" . $value["action"]; } return $new_data; } } return false; }
并且在$this->auth->check()這行替換成了in_array($ctrl . "/" . $action, $operation_list。這樣就差不多了。
運(yùn)行一看,速度也挺喜人。竟然達(dá)到了0.014,比最原始的快了百倍不止。
然后再去看網(wǎng)頁(yè)運(yùn)行,發(fā)現(xiàn)我優(yōu)化的這塊,明顯比網(wǎng)頁(yè)上的其他模塊加載速度要快了許多(因?yàn)轫?xiàng)目用了iframe),之前是其他模塊的內(nèi)容出來了,頭部的菜單還沒出來?,F(xiàn)在的情況恰恰相反,頭部菜單最先加載出來,然后等待其他iframe的加載。
做完這番工作,長(zhǎng)舒一口氣,這一番coding沒有白費(fèi)。
總結(jié)從這個(gè)例子中,我們可以得到一些,代碼優(yōu)化的技巧:
減少數(shù)據(jù)庫(kù)的操作
好像就只有這個(gè)吧....2333333
思考能不能夠繼續(xù)優(yōu)化呢?放在緩存中會(huì)如何?
如果放在緩存中的話,也不是不行,但是這里有一個(gè)點(diǎn)就是這里的待辦事項(xiàng)是可變的。而且項(xiàng)目中也沒有使用socket的技術(shù)。如果單單存儲(chǔ)在緩存中的話,那么更新緩存里的這塊數(shù)據(jù)就會(huì)變得更加啰嗦。索性就暫時(shí)這樣放著,能以后性能指標(biāo)提高了,再來優(yōu)化。
結(jié)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/29082.html
摘要:原文出處這種垃圾收集器的官方名稱是。使用收集器的名稱。事件時(shí)長(zhǎng)記錄不同的類型回收期間垃圾收集器線程消耗事件調(diào)用操作系統(tǒng)活著等待系統(tǒng)事件消耗時(shí)間應(yīng)用停頓的時(shí)鐘時(shí)間。現(xiàn)在我們看一些一些任務(wù)的時(shí)間,垃圾收集器線程等待很長(zhǎng)時(shí)間。 原文出處:Concurrent Mark and Sweep 這種垃圾收集器的官方名稱是Mostly Concurrent Mark and Sweep Garbag...
摘要:效果預(yù)覽按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽??山换ヒ曨l此視頻是可以交互的,你可以隨時(shí)暫停視頻,編輯視頻中的代碼。最后,把擺線的數(shù)量調(diào)整為個(gè)。 showImg(https://segmentfault.com/img/bVbe6re?w=400&h=301); 效果預(yù)覽 按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽。 https://code...
摘要:效果預(yù)覽按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽。可交互視頻此視頻是可以交互的,你可以隨時(shí)暫停視頻,編輯視頻中的代碼。最后,把擺線的數(shù)量調(diào)整為個(gè)。 showImg(https://segmentfault.com/img/bVbe6re?w=400&h=301); 效果預(yù)覽 按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽。 https://code...
摘要:原文出處設(shè)計(jì)的一個(gè)重要目標(biāo)是設(shè)置階段的持續(xù)時(shí)長(zhǎng)和頻率,因?yàn)槔占骺深A(yù)測(cè),可配置。收集器盡自己最大努力高概率實(shí)現(xiàn)目標(biāo)但不是必然,它會(huì)是硬實(shí)時(shí)。因此名稱是收集器。運(yùn)行不同使用獨(dú)立的收集器。 原文出處:G1 – Garbage First G1設(shè)計(jì)的一個(gè)重要目標(biāo)是設(shè)置stop-the-world階段的持續(xù)時(shí)長(zhǎng)和頻率,因?yàn)槔占骺深A(yù)測(cè),可配置。事實(shí)上,G1是一款軟實(shí)時(shí)的收集器,意味著你...
閱讀 3414·2021-10-08 10:15
閱讀 5627·2021-09-23 11:56
閱讀 1479·2019-08-30 15:55
閱讀 456·2019-08-29 16:05
閱讀 2738·2019-08-29 12:34
閱讀 2052·2019-08-29 12:18
閱讀 925·2019-08-26 12:02
閱讀 1661·2019-08-26 12:00