Slide 1

Slide 1 text

型チェック 速度改善 奮闘記⌛ 2024/11/16 TSKaigi Kansai Kenta TSUNEMI

Slide 2

Slide 2 text

Kenta TSUNEMI @tocomi 🏢テックタッチ株式会社 󰞵フロントエンドエンジニア 🏡 東京都在住 栃⽊県出⾝ 趣味 🏇 競⾺ ⚾ 野球観戦(ヤクルトファン) 🃏 ポーカー 🎮 ゲーム ⾃⼰紹介

Slide 3

Slide 3 text

今⽇は Typescript の型チェックの速度改善に取り組んだ話をします! 結果について触れつつ、特に調査のプロセスに重点を置いて話そうと思います💪 この発表が、みなさんが Typescript の速度改善に取り組むきっかけや⼿助けに なれば嬉しいです😌 今⽇の発表について

Slide 4

Slide 4 text

⽬次 🔖 1. 背景 2. 改善活動の結果 3. 調査プロセス

Slide 5

Slide 5 text

Nx を使ったモノレポで管理 3 つのアプリケーションと、100 近くのモ ジュールが存在している⚙ アプリケーションからモジュール、および モジュール同⼠の依存がある プロダクトの成⻑と共にモジュールの数が 絶賛増加中🔥 アプリケーションの概観 ※画像はイメージです

Slide 6

Slide 6 text

プロダクトが成⻑していくにつれて、Typescript の型チェックに時間がかかると いう声がチーム内で上がってくるように、、 ただ普段の開発業務に追われていて対応が後回しになっていた🥲 VSCode が重くなったらとりあえず restart ts-server しとけば良いという⾵潮 そこで速度改善に取り組んでみることにした💪 課題

Slide 7

Slide 7 text

⽬次 🔖 1. 背景 2. 改善活動の結果 3. 調査プロセス

Slide 8

Slide 8 text

各アプリケーションで tsc の速度を計測 したところ、 いずれのアプリケーションも⼀定の速 度改善を達成🥳🥳🥳 改善結果 Before After Diff App 1 40.21s 23.77s -41% App 2 27.01s 14.16s -48% App 3 14.54s 10.68s -27%

Slide 9

Slide 9 text

改善の要因は⼤きく分けると 3 つ👀 (パーセンテージは改善内容における割合) 1. Redux Toolkit 関連の型推論 (80%!!) 2. Extract による Instantiation (10%) 3. 不要なスキーマ定義‧チェック (10%) 改善の要因

Slide 10

Slide 10 text

各 reducer の引数にある state につ いて、 initialState の型を元に推論される部 分に時間がかかっていた。 (Redux Toolkit は v2.1.0) Redux Toolkit (Before)

Slide 11

Slide 11 text

initialState の型に名前を付けて切り 出すことでキャッシュが効くように なり速度が改善。 1 ファイルにつき 800 - 900ms か かっていたのが 数 ms 程度で済むよ うに🚀 Typescript の Performance に関す る公式 doc にある Naming Complex Types のパターン Redux Toolkit (After)

Slide 12

Slide 12 text

Redux Toolkit (Before / After) ある App の型チェックをトレースしたときの Before / After ⻘線で囲んだ部分が Redux Toolkit の createSlice をしているファイル

Slide 13

Slide 13 text

export function getNode( condition: Condition, nodeId: NodeId, type?: T ): Extract export function getNode( condition: Condition, nodeId: NodeId, type: Node['type'] ) { // do something } 引数で受け取った type (リテラル 型の union) をもとに Extract した 型を返却する関数。 Extract の部分 でチェックに時間を要していた。 Extract (Before)

Slide 14

Slide 14 text

export function getNode(condition: Condition, nodeId: NodeId): Node export function getNode( condition: Condition, nodeId: NodeId, type: 'condition' ): ConditionNode export function getNode( condition: Condition, nodeId: NodeId, type: 'operator' ): OperatorNode export function getNode( condition: Condition, nodeId: NodeId, type?: Node['type'] ) { // do something } 関数オーバーロードを利⽤すること で戻り値の情報は維持しつつ Extract を排除して、速度も改善🚀 コードが増え汎⽤性も失われたが、 このドメイン領域では type は 2 種 類しか存在せず、今後増えることも 考えにくかったため、 トレードオフを受け⼊れた形。 Extract (After)

Slide 15

Slide 15 text

ある型(右図では最終⾏の Types) を定義するときに、不要な zod schema (最後から 2 ⾏⽬)を介して いた🥲 不要なスキーマ定義 export const typeASchema = z.object({ type: z.literal('A'), name: z.string(), }); export type TypeA = z.infer; export const typeBSchema = z.object({ type: z.literal('B'), count: z.number(), }); export type TypeB = z.infer; export const typeCSchema = z.object({ type: z.literal('C'), value: z.string(), }); export type TypeC = z.infer; // ↓この typesSchema はただの中間介在物でしかない、、 const typesSchema = z.union([typeASchema, typeBSchema, typeCSchema]); export type Types = z.infer;

Slide 16

Slide 16 text

弊社では、とある理由からリポジトリにビルド済みの js ファイルを複数配置して いる。 それらは型チェックをする必要がないファイルだが、tsc コマンドの対象に含ま れてしまっていた😇 不要な型チェック

Slide 17

Slide 17 text

tsc の速度は⼀定改善できたものの、 VSCode 上でチェックに時間がかかるファイルは依然として存在している😭 ドメインのコアになる部分にチェックに時間がかかる複雑な型があるので、 それらを利⽤していると芋づる式にチェックに時間がかかってしまう⏰ (tsc はキャッシュが効くが、VSCode 上でのチェックは毎回全部⾛る) 今回紹介したのは型の使い⽅を⼯夫することで速度を改善した例だが、 この先は型⾃体に向き合う必要があるんだろうなと思っている💪 これからの課題

Slide 18

Slide 18 text

⽬次 🔖 1. 背景 2. 改善活動の結果 3. 調査プロセス

Slide 19

Slide 19 text

⼀枚絵で調査の流れ

Slide 20

Slide 20 text

⼀枚絵で調査の流れ

Slide 21

Slide 21 text

型チェックのパフォーマンスデータを取得するには 2 つの⽅法がある tsc コマンドで出⼒ プロジェクト全体のファイルを対象にチェックが⾛る。 全体におけるボトルネックを把握するのに有⽤。 VSCode 上で出⼒ 現在開いているファイルを起点に関連するファイルのチェックが⾛る。 ファイル保存時の型チェックのボトルネックを特定するのに有⽤。 型チェックに関するデータの取得

Slide 22

Slide 22 text

出⼒されるファイルは 2 種類📄 調査⽤ファイル trace.json どのファイルにどれだけ時間が費やされたかの詳細ログ 最重要ファイルと⾔っても過⾔ではない! types.json チェック対象になった型の⼀覧。特定の調査に必要。 時として超巨⼤なファイルになる⚠

Slide 23

Slide 23 text

--noEmit ビルド結果の js ファイルを出⼒しない --extendedDiagnotics tsc コマンドの対象ファイル数や各プロセスにかかった時間等が表⽰される 修正の結果、速度や Instantiations の数が変化したか確認するのに便利 --generateTrace 指定したディレクトリに trace.json と types.json を出⼒する 速度改善には⽋かせない超重要オプション💡 tsc コマンドでのデータ取得 npx tsc -p path/to/tsconfig.json --noEmit --extendedDiagnostics --generateTrace traceDir

Slide 24

Slide 24 text

VSCode のオプションを使う場合、 右図のように設定する。 ログの位置が⾮常にわかりにくいが、 コマンドパレットで tsserver.log を開いて、 そこからエクスプローラを開くことで ログのありかに辿り着ける。 VSCode を開き続けている間はログが取得され続 け、任意の期間だけログを取る事ができない😇 VSCode でのデータ取得(オプション) { "typescript.tsserver.enableTracing": true, "typescript.tsserver.log": "verbose" }

Slide 25

Slide 25 text

VSCode の拡張機能を使うことでも データ取得が可能。オススメ! 前回の TSKaigi で登壇されていた ypresto さん作成の拡張機能! コマンドパレットから任意のタイミン グで trace ログの ON/OFF を切り替え られる👏 これのおかげで調査がめちゃ捗りまし た、ありがたやありがたや🙏 VSCode でのデータ取得(拡張機能)

Slide 26

Slide 26 text

⼀枚絵で調査の流れ

Slide 27

Slide 27 text

trace.json の解析にはツールを利⽤する about://tracing ブラウザに標準搭載されている。開くと Perfetto UI をオススメしてくる。 Perfetto UI Web アプリ。もともと Android ⽤のツールだった模様。 Speedscope ⭐ オススメ!軽くて操作も直感的で⼀番使いやすかった。 trace.json の解析

Slide 28

Slide 28 text

trace.json の解析 型チェックに時間がかかっているファイルが帯の⻑さから特定できる

Slide 29

Slide 29 text

⼀枚絵で調査の流れ

Slide 30

Slide 30 text

ファイルのどこに時間がかかっているか特定するために下⽅向に進んでいく🛤 pos と end は AST 形式での位置を⽰しているので、AST 解析ツールを使って trace ファイルの内容とソースコードを照合していく🧐 AST でのポジション特定

Slide 31

Slide 31 text

structuredTypeRelatedTo を深掘りするためには、 どこを参照しているのか知るために types.json を⾒る必要がある🔍 types.json

Slide 32

Slide 32 text

types.json にはチェック対象となった型情報がすべて出⼒されるため、 対象によっては超巨⼤ファイルになってしまう⚠ ⾃分の環境では、3GB ほどのファイルが出⼒されたりした😇 そうなると出⼒にも時間もめっちゃかかる、、 trace.json と types.json の出⼒はシーケンシャルに⾏われるので、types.json の出⼒が終わるまで tsc コマンドは実⾏中になってしまうのにも注意。 types.json の注意

Slide 33

Slide 33 text

調査対象によっては types.json は不要 な場合もある。 最初は trace.json の出⼒が終わったタ イミングでいちいちプロセスを kill し たりしていたが、 tsc のコードに⼿を加えてしまうのが⼀ 番⼿っ取り早かった🔧 node_modules/typescript/lib/tsc.js の dumpTypes() をコメントアウト🥷 types.json が不要な場合は… [email protected] の例

Slide 34

Slide 34 text

調査の流れ おさらい

Slide 35

Slide 35 text

デモ 調査の流れを簡単にデモしてみます🤖 (時間なかったらスキップ)

Slide 36

Slide 36 text

とにもかくにもまずは計測してみることが⼤事☝ やみくもに⼿を打つのではなく、影響の⼤きいボト ルネックから⽴ち向かっていこう💪 まとめ

Slide 37

Slide 37 text

おわり