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