自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一行代碼提升 30%+ TS 類型檢查性能

開發(fā) 前端
先說結(jié)論,強烈建議在所有復雜泛型場景中,顯式提供泛型參數(shù),這能夠非常顯著降低泛型類型推斷的復雜度,進而提升 TS 性能,幅度甚至可能達到50%!

前言

先說結(jié)論,強烈建議在所有復雜泛型場景中,顯式提供泛型參數(shù),這能夠非常顯著降低泛型類型推斷的復雜度,進而提升 TS 性能,幅度甚至可能達到50%!例如,在使用 @douyin-fe/semi 庫的 Form 組件時:

  • 未提供泛型參數(shù):

圖片圖片

圖片圖片

  • 提供泛型參數(shù):

圖片圖片

圖片圖片

在未顯式提供泛型參數(shù)時,構(gòu)建耗時大約為2.3s,其中有 850ms 消耗在 checkSourceFile 節(jié)點上;而主動提供泛型參數(shù)后,構(gòu)建總耗時下降至 1.5s,降幅達到 34%,而這僅僅只需要修改一行代碼即可實現(xiàn)!

那么,為什么會有如此巨大的提升呢?接下來,我會詳細總結(jié)整個分析排查問題的過程與工具,以及后續(xù)在工程層面,可以做那些事情防止再次出現(xiàn)同類問題。

TS Check 性能排查方法

工欲善其事必先利其器,首先,我們需要學習如何獲取 TSC 執(zhí)行的性能數(shù)據(jù),而這需要用到兩個 TSC 命令行參數(shù):

  • --generateTrace:用于 trace-xxx.json 文件,包含 TSC 編譯過程中關(guān)鍵節(jié)點的性能數(shù)據(jù),可使用 SpeedScope 工具可視化分析:

圖片圖片

  • --generateCpuProfile:用于生成詳細的 CPU 執(zhí)行堆棧信息,同樣可以使用 SpeedScope 工具做可視化分析:

圖片圖片

關(guān)于這兩個參數(shù)更詳細的解釋,可參考 TS 官方文檔 Performance Tracing。回到項目中,使用這兩個參數(shù)執(zhí)行類型檢查,并將結(jié)果寫出到 ts-trace 目錄:

tsc -b tsconfig.build.json --generateTrace ./ts-trace --generateCpuProfile ./ts-trace/ts.cpuprofile --force

之后打開 SpeedScope 工具,選擇相應(yīng)文件即可。順便提一下, SpeedScope 是我用過最好的 CPU Profile 分析工具,比 TS 文檔推薦 chrome://tracing 效率高很多,建議優(yōu)先使用。

我個人的使用經(jīng)驗:先看 trace-xxx.json 文件,再看 cpuprofile 文件。因為 trace-xxx.json 信息更聚焦一些,相對能直觀發(fā)現(xiàn)問題,例如上圖中 checkSourceFile 節(jié)點明顯比其他節(jié)點長很多,肉眼可見是一個異常點;而 cpuprofile 包含了 TSC 執(zhí)行過程中大部分調(diào)用堆棧,信息更全,更適合深入分析執(zhí)行細節(jié),定位問題的具體原因,例如識別出上述 trace-xxx.json 中的 checkSourceFile 異常點后,可在 cpuprofile 中找到對應(yīng)函數(shù)執(zhí)行堆棧,向下分析具體性能卡點。

問題分析

基于上述生成的數(shù)據(jù),我們可以初步定位到 checkExpression 節(jié)點有明顯的性能問題,在示例中消耗 607ms,占比 25% 之久:

圖片圖片

根據(jù)堆棧信息中 path/pos 等字段,可定位到問題出現(xiàn)在下圖第 13 行:

圖片圖片

據(jù)此可初步推斷,tsc 在檢查表達式 <Form onSubmit={handleSubmit}> 語句時存在較大的性能損耗,而這段代碼與其他代碼最大的差異在于:1. 它用了 Form 元素;2. 它沒有顯式聲明 Form 泛型參數(shù)。

至此,答案就大概可以“猜”出來了,試著補上泛型參數(shù),這段 checkExpression 的時間直接從 607ms 降低到 79ms:

圖片圖片

原理淺析

到這里,已經(jīng)初步找到這個問題的表征答案,但更重要的是:為什么一個泛型參數(shù)的缺失會導致如此嚴重的性能問題?只有透徹地理解性能卡點的底層原理,才能推導出正確且完善的解決方案,而要分析問題的根因,有兩種方法,一是從頭開始仔細閱讀并理解源碼,但 TS 項目太大,成本太高;二是分析上述 --generateCpuProfile 參數(shù)所生成的 Cpu 調(diào)用棧文件,理解這部分耗時操作里都做了那些事情,這明顯性價比要高出許多。

所以,接下來使用 SpeedScope 打開 CpuProfile 文件后,根據(jù)時間定位到 checkExpression 對應(yīng)的 CPU 堆棧節(jié)點:

圖片圖片

可以看到,這下面有一個非常長的函數(shù)堆棧列表,特別是遞歸出現(xiàn)了許多次 checkExpression、 instantiateXXX 等函數(shù),性能問題應(yīng)該就出現(xiàn)在這里。作為對比,補充泛型類型后,相應(yīng)調(diào)用堆棧簡化為:

圖片圖片

仔細對比發(fā)現(xiàn),兩者邏輯分叉點主要出現(xiàn)在 chooseOverload 函數(shù)上:

  • 優(yōu)化前:

圖片圖片

  • 優(yōu)化后:

圖片圖片

接著嘗試斷點調(diào)試 chooseOverload 函數(shù),排查過程比較繁瑣,就不展示了,直接拋結(jié)論,該函數(shù)大致做了下面這些事情:

  1. TS 執(zhí)行過程中,遇到泛型定義時調(diào)用 chooseOverload,函數(shù)內(nèi)判斷是否傳入泛型參數(shù)(下圖 75424 行);若參數(shù)為空,則調(diào)用 inferJsxTypeArguments 推斷類型(下圖 75436 行);

圖片圖片

  1. 而 inferJsxTypeArguments 內(nèi)部遍歷 jsx 定義的 attributes ,逐步校驗各個組件 Props 的類型定義;

圖片圖片

  1. 當遇到 onValueChange、onSubmit 等函數(shù)類型的 props 時,TS 內(nèi)部需要進一步推斷這類函數(shù)簽名,最終走到 checkFunctionExpressionOrObjectLiteralMethod 函數(shù);
  2. 而 checkFunctionExpressionOrObjectLiteralMethod 內(nèi)部會遞歸調(diào)用多次 checkExpression 函數(shù),經(jīng)過一段非常復雜的計算后,最終推斷出函數(shù)簽名,之后再與 Form 元素的 Value 泛型對比檢查類型匹配度。

由此可推斷,此處性能卡點主要出現(xiàn)在 Form 元素的 Value 泛型推斷,以及對傳遞給 Form 元素的各類 onValueChange 等函數(shù)類型的 Props 的泛型推斷與檢測上,只需要簡單提供 Value 泛型,即可繞過許多推斷步驟,進而提升效率。

需要注意的是,這一問題目前只在 Form 組件出現(xiàn),其它多數(shù)帶泛型參數(shù)的簡單組件即使觸發(fā)了推斷邏輯,由于類型邏輯相對簡單許多,校驗鏈路較短,并不會導致性能問題。

圖片圖片

另外還需要注意,chooseOverload 函數(shù)中還包含了另一層用于處理函數(shù)重載的循環(huán)邏輯:

圖片圖片

實測發(fā)現(xiàn),函數(shù)重載數(shù)量越多,參數(shù)形態(tài)越復雜,此處性能越差,例如下面例子中:

圖片圖片

圖片圖片

這里的卡點在于 I18nKeysNoOptionsType 是一個非常長達 12000+ 的靜態(tài)字符串數(shù)組,在上述實例中,TS 需要循環(huán)校驗 t 函數(shù)的重載簽名,并在每次校驗時遍歷驗證這 12000+ 靜態(tài)字符串,兩相疊加導致性能成本居高不下:

圖片圖片

防劣化

到此,我們已經(jīng)完全可以確定問題根因出在源碼中泛型參數(shù)缺失,導致 Typescript 需要做 復雜泛型類型的推導與檢查,引發(fā)性能問題,只需借助 Typescript 的 Performance Trace 找出這類性能卡點,補充相應(yīng)泛型參數(shù)即可。但更重要的是,修復存量問題后,后續(xù)如何防止這類問題再次出現(xiàn)呢?有幾種方案:

  • 文檔化,約束代碼規(guī)范;
  • ESLint 檢測并攔截特定模式代碼;
  • CI 階段分析 TS 性能數(shù)據(jù),攔截導致長任務(wù)的代碼;

首先,最簡單也是成本最低的方法,可以將相關(guān)規(guī)則提升為團隊開發(fā)規(guī)范,明確要求開發(fā)者在那些情況下必須補充完備的泛型參數(shù),但這種方式本質(zhì)上屬于“軟性約束”,執(zhí)行與否完全取決于開發(fā)者的狀態(tài),考慮到人類智能的隨機性,最終效果往往并不理想,更好的方式是使用自動化工具在 CI 階段自動檢測問題實現(xiàn)更“強”的約束。

具體來說,可以選擇編寫 ESLint 規(guī)則,限定某些 Case 必須提供泛型參數(shù),例如:

import { Rule } from 'eslint';

export const enforceTsGenericRule: Rule.RuleModule = {
  meta: {
    type: 'problem',
    // ...
  },
  create(context) {
    return {
      JSXOpeningElement(node) {
        if (
          node.name.type === 'JSXIdentifier' &&
          node.name.name.toLowerCase() === 'form'
        ) {
          const hasGeneric =
            node.typeParameters && node.typeParameters.params.length > 0;
          if (!hasGeneric) {
            context.report({
              node,
              message: 'Form elements must have generic parameters.',
            });
          }
        }
      },
    };
  },
};

但問題在于,這種方式必須先提前找出所有可能引發(fā)性能劣化問題的代碼模式,整體僵化不靈活,容易導致遺漏或誤傷,相對還不夠極致。

更好的方式是在 CI 環(huán)境增量分析 TS 執(zhí)行性能數(shù)據(jù),分析并攔截導致長任務(wù)的代碼,實現(xiàn)邏輯:

  1. CI 環(huán)境中執(zhí)行 tsc -b tsconfig.build.json --generateTrace ./ts-trace,生成性能數(shù)據(jù),注意不要加 --force 參數(shù);

圖片圖片

  1. 遍歷 trace-xx.json 文件,找到所有 name === "checkExpress" && dur > threshold 的節(jié)點,取出對應(yīng) path 與 pos 數(shù)值;
  2. 根據(jù) path 與 pos 數(shù)值定位對應(yīng)代碼行, 調(diào)用 git diff source-branch...target-branch 取得增量內(nèi)容,之后判斷長任務(wù)對應(yīng)代碼行是否為本次更新代碼,若命中則調(diào)用 CI 接口進行攔截。

總結(jié)

對于大規(guī)模項目而言,Typescript 很好,我認為幾乎是必選技術(shù)棧之一,并且有必要在開發(fā)環(huán)境、CI/CD 各個環(huán)節(jié)設(shè)置卡口,驗證代碼的正確性,其本身性能也做的非常極致,但架不住大型項目代碼量上來之后,任務(wù)復雜度過高導致類型檢測成本也居高不下,此時就必須從代碼本身著手,做好各類性能優(yōu)化,保證時間復雜度在合理范圍內(nèi)。

但這個方向資料并不多,很少能找到現(xiàn)成且有效的解決方案,多數(shù)時候需要自己摸索。過去這段時間,我們團隊也做了許多這方面的嘗試,除了本文提到的這種顯式定義泛型參數(shù)的方法外,其他值得分享的性能優(yōu)化手段包括:

  • 使用 tsc 緩存,復用舊的結(jié)果;
  • 使用 ts project references,實現(xiàn)分片檢測;
  • 正確配置 watchOption 屬性,減少文件監(jiān)聽復雜度;
責任編輯:武曉燕 來源: Tecvan
相關(guān)推薦

2016-12-02 08:53:18

Python一行代碼

2025-02-12 09:55:01

Java代碼性能

2021-09-22 09:43:47

Python 開發(fā)編程語言

2014-02-12 13:43:50

代碼并行任務(wù)

2022-04-09 09:11:33

Python

2017-04-05 11:10:23

Javascript代碼前端

2020-03-26 12:38:15

代碼節(jié)點數(shù)據(jù)

2020-07-22 08:30:02

代碼開發(fā)工具

2021-08-31 09:49:37

CPU執(zhí)行語言

2017-04-13 19:20:18

Python代碼并行任務(wù)

2021-11-02 16:25:41

Python代碼技巧

2020-08-19 10:30:25

代碼Python多線程

2020-09-09 16:00:22

Linux進程

2022-12-30 08:08:30

2020-02-06 11:15:58

Python命令代碼

2023-09-12 10:10:57

開發(fā)者工具開源

2023-10-23 09:56:00

2025-04-16 09:21:00

2019-12-25 14:08:50

Pandas數(shù)據(jù)計算

2020-08-12 14:54:00

Python代碼開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號