優化 JS 程式的一個小方法

来源:https://www.cnblogs.com/88223100/archive/2022/10/04/A-Small-Method-of-Optimizing-JS-Program.html
-Advertisement-
Play Games

就像在學習之前先要識字,我想在介紹優化 JavaScript 代碼之前,先介紹一下自己對編程語言的理解。故事要從一隻叫做 Theseus 的機械鼠和其發明人克勞德-香農(Claude Shannon)說起。在傳記《A Mind at Play:How Claude Shannon Invented ... ...


 

 就像在學習之前先要識字,我想在介紹優化 JavaScript 代碼之前,先介紹一下自己對編程語言的理解。故事要從一隻叫做 Theseus 的機械鼠和其發明人克勞德-香農(Claude Shannon)說起。在傳記《A Mind at Play:How Claude Shannon Invented the Information Age》中,作者 Jimmy Soni 和 Rob Goodman 強烈希望將香農的作品 Theseus 展示給廣大讀者。面對複雜的迷宮,Theseus 僅用一堆繼電器、ROM 存儲等簡單而古老的電子元器件,就完成了對複雜迷宮的探索和成功線路的記憶,第二次沿著正確道路走出迷宮的 Theseus 沒犯一點兒錯誤。大多數人認為這不過是騙人的把戲和小玩意兒,棄之如敝履。少數聰明人眼裡 Theseus 蘊含的驚人智慧簡直可以和牛頓、愛因斯坦媲美,香農憑藉一己之力將布爾代數引入電子電路設計啟發了後世數字電路乃至電腦的發明。

數字電路和程式的關係

 

 

 

                                                       圖 4-44 我在 2011 年做的智能調光調色電路

如圖 4-44 就是一個在香農啟發下產生的數字電路,Theseus 中古老的電子元器件已經變成了右側紅框的集成電路。通過將兩個電容和一個晶振組成左側紅框的振蕩電路,給集成電路提供時鐘。集成電路上有數模轉換電路,把高低電平代表的布爾代數運算電路計算結果轉換成模擬信號輸出,可調光調色的 LED 模組上就產生了功率輸出的不同:顏色、頻率輸出的不同:亮度。在香農的啟發和幫助下,圖 4-44 的電路實現了一個完整的程式功能:隨時鐘變化不斷改變 LED 顏色和亮度。

如果圖 4-44 的完整程式直接通過翻查集成電路手冊,按照圖 4-45 對照引腳定義和手冊里對寄存器操作的地址寫入對應的指令和數據。

 

 

                         圖 4-45 我的電路中用到的集成電路手冊信息(摘錄自 ATMEL 官網) 

從這個例子中可以看到,在香農的啟發和幫助下,數字電路最大的好處就是把電路進行了抽象,讓程式邏輯和數字電路在這個抽象層面上統一起來。在這個新的統一抽象層面上,程式邏輯的控制可以類比為邏輯電路中的控制,程式的輸入輸出可以類比為數字電路中的存儲(ROM、RAM)。時鐘電路給數字電路中信號傳輸提供標尺,中央處理器根據這些標尺來控制數字電路中信號的傳輸和流轉,這種傳輸和流轉則把點狀的控制變成控制流,把點狀的存儲變成數據流。因此,在程式中最重要的是控制流和數據流。

為了不必每次都查手冊用引腳去燒寫 ROM 為數字電路註入控制指令和數據,前輩們發明瞭一套燒寫系統,用彙編或 C 語言來定義和描述控制流和數據流,再由編譯器翻譯成圖 4-45 對應的寄存器地址和指令、數據,然後通過燒寫器變成數字信號通過集成電路引腳傳輸到集成電路內部完成程式的燒寫。這套系統讓我們擺脫手冊(當然不是完全擺脫,有時候還是要查但頻率大幅降低)直接用變成語言去描述程式,再通過模擬器(類似於前端 MOCK 數據)完成調試和模擬測試,讓我們對數字電路編程變得異常簡單。

 

 

                        圖 4-46 對數字電路進行編程(摘自 ATMEL 官網)

如圖 4-46 這種方式控制數字電路就容易多了,您可以在淘寶買一個 Arduino 開發板,然後按照下麵的代碼自己試試從本質上理解程式是什麼?數字電路是什麼?計算的本質是什麼?

unsigned long colorT[] = {  0xff3300,0xff3800,0xff4500,0xff4700,0xff5200,0xff5300,0xff5d00,0xff5d00,0xff6600,0xff6500, 0xff6f00,0xff6d00,0xff7600,0xff7300,0xff7c00,0xff7900,0xff8200,0xff7e00,0xff8700,0xff8300, 可以自己繼續添加
}
int R_Pin = 11;
int G_Pin = 10;
int B_Pin = 9;
// 這裡就是手冊中集成電路輸出信號的引腳和 LED 模塊連接方式對應
int red,green,blue = 0;
int i = 0;
int l = sizeof(colorT);
void setup(){
  pinMode(12, OUTPUT);
  pinMode(R_Pin, OUTPUT);
  pinMode(G_Pin, OUTPUT);
  pinMode(B_Pin, OUTPUT);
  digitalWrite(12, LOW);
}
void setColor(int redValue, int greenValue, int blueValue){
  analogWrite(R_Pin, redValue);
  analogWrite(G_Pin, greenValue);
  analogWrite(B_Pin, blueValue);
}
void  loop(){
  red = (colorT[i] >> 16) & 0xff;
  green = (colorT[i] >> 8) & 0xff;
  blue = (colorT[i] >> 0) & 0xff;
  setColor(red, green, blue);
  i++;
  if(i >= l){
    i = 0;
  }
  delay(200); // 控制時鐘信號
}

神游了一圈兒,接下來我們來看看如何觀察 JavaScript 的控制流和數據流。前面提到要用 parser 對原始的代碼文本(字元串)進行處理,最常見的處理目的是生成“抽象語法樹”(AST)。當然,對於 D2C Schema 和 DesignToken 的 parsing 是為了輸出正確的、內聯 CSS 的完整 HTML 文檔。

 

抽象語法樹 AST

 

之所以要把 JavaScript 代碼文本轉換成 AST 是因為編譯器無法對字元串構成的程式文本進行直接操作,只有把程式文本從“1+2”變成new BinaryExpression(ADD, new Number(1), new Number(2))這種形式,才能被編譯器理解。怎麼樣?是不是和 ATMEL 集成電路的編程很像?操作ADD和數據new Number(1)。因此,從程式文本到 AST 的過程可以類比成解碼過程 parsing,用於解碼的那段兒代碼就叫做 parser。知道了這些,我們就可以把程式文本也就是經常掛在嘴邊的“代碼”翻來覆去的玩兒,代碼文本變成 AST 推薦 esprima 這個工具,遍歷 AST 節點併進行一些修修補補(優化性能?),最後把修改過的 AST 再轉換成代碼文本可以用 escodegen 。

 
// 生成 AST 抽象語法樹
const esprima = require('esprima');
const AST = esprima.parseScript(jsCode);
// 遍歷和修改 AST
const estraverse = require('estraverse');
const escodegen = require('escodegen');
function toEqual(node){
  if(node.operator === '=='){
    node.operator = '===';
  }
}
function walkIn(ast){
  estraverse.traverse(ast, {
    enter: (node) => {
      toEqual(node);
    }
  });
}
// 從 AST 進行代碼生成
const escodegen = require('escodegen');
const code = escodegen.generate(ast);

具備上面的技能後,讓我們那一段兒真實的代碼來練練手。

 
acc = 0;
i = 0;
len = loadArrayLength(arr);
loop {
  if (i >= tmp)
    break;

  acc += load(arr, i);
  i += 1;
}

用 esprima 提供的 parser 把這段兒代碼轉換成 AST 後,我藉助 GraphViz 工具把 AST 從 JSON 格式轉換成 digraph 格式的 .gv 文件,然後生成圖 4-47。

 

 

                                                  圖 4-47 對 AST 進行可視化

數據流圖 DFG

圖 4-47 是一棵樹,所以能很方便的進行遍歷,當我們訪問 AST 節點時生成對應的機器代碼。這個方法的問題在於,關於變數的信息非常稀少,並分散在不同的樹節點上。為了優化安全地將長度查找移出迴圈,我們需要知道數組長度不會在迴圈迭代之間變化。人類只需查看源代碼即可輕鬆完成,但編譯器需要做大量工作,才能自信地直接從 AST 中提取這些事實。與許多其他編譯器問題一樣,這通常通過將數據提升到更合適的抽象層,即中間表示(IR)來解決。在這個特定情況下,IR 的選擇被稱為數據流圖(DFG)。與其談論語法實體(如for loop、expressions、...),我們應該談論數據本身(讀取、變數值),以及它如何在程式中變化。

在我們的特定示例中,我們感興趣的數據是變數arr的值。我們希望能夠輕鬆觀察它的所有使用,以驗證沒有越界訪問或任何其他更改來修改數組的長度,這是我們優化的前提。通過引入不同數據值之間的“使用”(定義和使用)關係來實現的。具體而言,這意味著該值已聲明一次(圖 4-47 中的_節點_),並且它已用於創建新值(圖 4-47 的_邊_)。顯然,將不同的值連接在一起將形成如圖 4-48 的數據流圖。

 

                                                        圖 4-48 數據流圖

註意數據流圖 4-48 中的紅色array框,離開它的實心箭頭表示此值的用法。通過在這些邊上迭代,編譯器可以導出array的值用於:

 

  • loadArrayLength

 

  • checkIndex

 

  • load


如果以破壞性方式訪問數組節點的值(即存儲、長度大小),則此類圖的構造方式是顯式“克隆”數組節點。每當我們看到array節點並觀察其用途時,總是確定它的值不會改變。這聽起來可能很複雜但很容易實現,該數據流圖遵循單一靜態分配(SSA)規則。簡而言之,要將任何程式轉換為 SSA,編譯器需要重命名變數的所有賦值和後續使用,以確保每個變數只分配一次。

例如,在SSA之前:

 
var a = 1;
console.log(a);
a = 2;
console.log(a);

SSA之後:

 
var a0 = 1;
console.log(a0);
var a1 = 2;
console.log(a1);

通過 SSA 後我們可以確定,當談論a0時實際上是在談論它的單個任務。

 

控制流圖 CFG

 

使用數據流分析來從程式中提取信息,使我們能夠就如何優化它做出安全假設。這種數據流表示在許多情況下非常有用,唯一的問題是通過將代碼轉換為數據流圖,在表示鏈(從源代碼到機器代碼)中與 AST 相比,這種中間表示更不適合生成機器代碼。由於程式邏輯是一個順序排列的指令列表,CPU 一個接一個地執行它,數據流圖似乎沒有傳達這一點。通常,通過將圖節點分組到塊中解決這個問題,這個表示形式稱為控制流程圖(CFG)。

 
b0 {
  i0 = literal 0
  i1 = literal 0

  i3 = array
  i4 = jump ^b0
}
b0 -> b1

b1 {
  i5 = ssa:phi ^b1 i0, i12
  i6 = ssa:phi ^i5, i1, i14

  i7 = loadArrayLength i3
  i8 = cmp "<", i6, i7
  i9 = if ^i6, i8
}
b1 -> b2, b3
b2 {
  i10 = checkIndex ^b2, i3, i6
  i11 = load ^i10, i3, i6
  i12 = add i5, i11
  i13 = literal 1
  i14 = add i6, i13
  i15 = jump ^b2
}
b2 -> b1

b3 {
  i16 = exit ^b3
}

如圖 4-49 所示,我們可以按照之前的方法把他編程一張控制流圖。

 

                                     圖 4-49 控制流圖

如您所見:塊b0中的迴圈前有代碼,b1中的迴圈頭,b2中的迴圈測試,b3中的迴圈主體,b4中的退出節點。從這個例子翻譯成機器代碼非常容易,將iXX替換為CPU寄存器名稱(就像前文在 ATMEL 手冊上查到的寄存器地址),併為每個指令逐行生成機器代碼。

CFG 具有數據流關係和順序,這使我們能夠將其用於數據流分析和機器代碼生成。然而,試圖通過操縱其中包含的塊及其內容來優化 CFG,會變得複雜且容易出錯。相反,Clifford Click 和 Keith D Cooper 提議使用一種叫做“節點海”的方法,來消除 CFG 和複雜的數據流圖帶來的麻煩。

 

節點海 Node Sea

 還記得帶有虛線的花哨數據流圖嗎?這些虛線實際上是使該圖成為節點海圖的原因。我們選擇將控制依賴項聲明為圖中的虛線邊緣,而不是將節點分組並對其進行排序。如果我們刪除所有未破線的東西,並稍微分組一些事情,我們將得到圖 4-50 所示的節點海圖。

 

                                                    圖 4-50 節點海(Node Sea)

圖 4-50 節點海是查看代碼非常強大的方式,它具有一般數據流圖的所有信息,無需不斷刪除/替換塊中的節點即可輕鬆更改以實現優化。節點海圖通常通過圖約簡進行修改,我們只需將圖表中的所有節點排隊,為隊列中的每個節點調用我們的函數,此函數涉及的所有內容(更改、替換)都將放入另一個隊列,稍後將傳遞給優化函數。如果您有許多優化點,例如:合併/減少網路請求、合併/減少 JSBridge 調用、合併/減少本地存儲 API 調用等,您可以將它們堆疊在一起,併在隊列中的每個節點上應用它們,如果它們依賴於彼此的最終狀態,您也可以逐一應用它們。

 

作者 | 甄焱鯤(甄子)

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/A-Small-Method-of-Optimizing-JS-Program.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 線性表 線性表的定義 特點: 存在唯一一個被稱為第一個的數據元素 存在唯一一個被稱為最後一個的數據元素 除了第一個元素之外,其他的數據元素都有唯一一個直接前驅 除了最後一個元素之外,其他的數據元素都有唯一一個直接後驅 定義:是由 $n(n\ge 0)$ 個相同的數據元素組成的有限序列 邏輯特征 ...
  • JPA 即Java Persistence API。是一款持久層框架,中文名Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到資料庫中。 ...
  • 漢諾塔問題 在經典漢諾塔問題中,有 3 根柱子及 n 個不同大小的穿孔圓盤,盤子可以滑入任意一根柱子。一開始,所有盤子自上而下按升序依次套在第一根柱子上(即每一個盤子只能放在更大的盤子上面)。移動圓盤時受到以下限制: (1) 每次只能移動一個盤子; (2) 盤子只能從柱子頂端滑出移到下一根柱子; ( ...
  • 一、進行線程池創建 import cn.hutool.core.thread.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; import org.springframework.aop.interceptor.AsyncUncaugh ...
  • 一. 怎麼開啟斷點調試? 隨著開發的深入,越來越覺得高效的調試方法是多麼的重要了,但我們一般上來就是敲一些代碼,誰會去靜下心來學一些看似沒什麼用的調試技巧呢?但這恰恰就是新手和老手之間的區別。 斷點調試是很簡單的,只需要點擊idea上方的小蟲子,啟動調試即可,如下所示。 這當然不是本文的重點,只是開 ...
  • 一:背景 1. 背景 前段時間有位朋友咨詢說他的程式出現了非托管記憶體泄漏,說裡面有很多的 HEAP_BLOCK 都被標記成了 Internal 狀態,而且 size 都很大, 讓我幫忙看下怎麼回事? 比如下麵這樣。 1cbea000: 42000 . 42000 [101] - busy (41fe ...
  • U盤自動讀寫的小玩意 共有四種方法(我知道的方法,全是轉載。轉載也很不易,可望給個硬幣) 方法一(vbs方法 全自動,轉載自bilibili 點我跳轉)文件下載鏈接(點我下載) 方法二(cmd方法 需手動,轉載自bilibili 點我跳轉)文件下載鏈接(點我下載) 方法三(python方法 全自動, ...
  • 5.MySQL常用函數 5.1合計/統計函數 5.1.1合計函數-count count 返回行的總數 Select count(*)|count (列名) from table_name [WHERE where_definition] 練習 -- 統計一個班級共有幾個學生 SELECT COUN ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...