小規模プログラムコンパイラのパフォーマンス最適化への道

著者 | マルコ

導入

アプレット コンパイラは、Baidu 開発者ツールのコンパイル構築モジュールであり、アプレット コードをランタイム コードに変換するために使用されます。事業開発に伴い、旧バージョンのコンパイラではコンパイルの遅さやメモリ使用量の多さなどの問題が発生していましたが、コンパイラを大規模に再構築し、自社開発アーキテクチャを採用し、マルチスレッド化、コード化など多くの最適化を行いました。キャッシュ、ソースマップ、パフォーマンスとメモリ使用量が大幅に改善されました。全文では、新バージョンのコンパイラの設計思想や最適化手法、一般的なパッケージングツールで活用できる技術的なポイントを紹介しています。

全文は 6629 ワード、推定読了時間は 17 分です。

01 序文

ミニ プログラム コンパイラは、ミニ プログラムの開発、プレビュー、リリースのすべての段階で必要となるため、コンパイラのパフォーマンスは開発者の開発効率と開発者ツールの使用体験に直接影響します。

古いバージョンのコンパイラ (webpack4 ベース) は、大規模なプロジェクトをビルドするときに非常に遅く、大量のメモリを使用するため、開発者から批判されてきました。多くの研究開発を経て、最終的に新しいコンパイルに完全に自社開発のアーキテクチャを採用し、小さなプログラム プロジェクトの構築に多くの最適化を行い、古いコンパイルの問題を基本的に解決しました。

次の図は、いくつかのプロジェクトの建設時間を比較したものです。

新しいバージョンのコンパイラは、旧バージョンと比較して 2 ~ 7 倍のパフォーマンス向上を実現し、リアルタイム コンパイルやホット リロードなどの機能をサポートし、メモリ消費量を削減してより良い製品を構築します。

以下では、フレームワークの選択、新しいコンパイラの動作原理、パフォーマンスと製品の最適化手法の側面から、新しいコンパイラの開発の軌跡を紹介します。

02 フレーム選択

新しいバージョンのコンパイラを設計するときは、現在の問題点であるパフォーマンスを明確にし、パフォーマンスの問題の解決に優先順位を付ける必要があります。コンパイラーに役立つ他の新しいテクニックやアイデアも実装されています。

webpack4 に基づく古いバージョンのコンパイラには次の問題があります。

  • 大規模なプロジェクトの構築が遅すぎます。

  • dev の起動が遅く、インクリメンタル コンパイルも遅く、ローダー キャッシュのみがサポートされており、キャッシュなしのバンドルも遅くなります。

  • webpack4 に基づく拡張機能の開発では、機能させるためにいくつかのモジュールにパッチを適用する必要があるため、メンテナンスが困難になります。

  • Webpack バンドル プロセスの一部はアプレット コード構造に対して最適化できず、無効なビルドがあります。

新しくコンパイルされた設計目標:

  • 完全なコンパイル速度が向上し、Webpack の無効なビルド プロセスが排除されます。

  • 完全キャッシュをサポートし、初回および増分コンパイルを高速化します。

  • リアルタイム コンパイルをサポートし、開発の起動時間と二次コンパイル時間を短縮します。

  • マルチスレッドのコンパイル高速化とページのホットリロードをサポートします。

  • 製品構造を最適化し、製品容積を削減します。

2.1 主流のビルドツール

以下に私たちが調査した主流のフロントエンド構築ツールを紹介しますが、各ツールには適用可能なシナリオ、利点と欠点があります。

新しいバージョンのコンパイラー アーキテクチャを設計するときは、他のビルド ツールの設計概念と技術的特徴を参照する価値があります。

Webpack のビルド プロセス:

Webpack の利点: 完全な機能、アクティブなコミュニティ、強力な構成可能性、および強力な拡張性。

Webpackのデメリット:構成が複雑、構築速度が遅い、二次開発が難しい。

区画構築プロセス:

Parcel の利点: 構成が不要、構築速度が速く、マルチスレッドとフル キャッシュのネイティブ サポート、マルチスレッド間のデータ共有は lmdb を通じて行われ、クロススレッド通信のオーバーヘッドが回避されます。

Parcel の欠点: エコシステムが小さい、カスタマイズが限定的、Node プラグインの多用、互換性の低さ。

Vite のビルドプロセス:

Vite の利点: 比較的シンプルな構成、オンデマンドでのコンパイル、高速な起動、開発中の優れたエクスペリエンス。

Vite の欠点: エコシステムが小さく、開発とリリースに 2 つのビルド プロセスが存在します。

その他のミニプログラム プラットフォーム:

  • WeChat は、gulp および C++ モジュールに基づいて小さなプログラムを構築し、より優れたパフォーマンスと開発エクスペリエンスを提供する npm モジュールを事前構築します。

  • Alipay は、webpack に基づいて小さなプログラムを構築し、コード圧縮を高速化するために esbuild を使用します。

  • Douyin ミニ プログラムは自社開発のコンパイラーを使用しており、構築プロセスは比較的簡単です。

2.2 新しいバージョンのコンパイラ

新しいコンパイル フレームワークを設計する際、私たちは主流のパッケージング ツールのワークフローから教訓を引き出し、ミニ プログラム コードの特性と組み合わせて、ミニ プログラムのパッケージング パフォーマンスの最適化に重点を置いてユニバーサル パッケージング ツールを作成しないことにしました。

最終的に、私たちは自社開発コンパイラのソリューションを選択し、多くの最適化作業を行いました。新しいバージョンのコンパイラの最適化ポイントには次の点が含まれます。

1. 複数のコンパイラが連携して、ダイナミック ライブラリ開発などの複数の種類のプロジェクトの構築を分離できるようにサポートします。

2. プロセス全体がコンパイル段階でキャッシュされるため、二次ビルド時間が 90% 以上節約されます。

3.dev 開発では、単一ページのコンパイルのパフォーマンスを向上させるために、デフォルトでオンデマンド コンパイルが使用されます。

4. babel および swc マルチスレッド コンパイルをサポートし、完全なコンパイル速度を 2 ~ 7 倍に向上させます。

5. 新しいバージョンのソースマップ プロトコルを採用し、不必要な解析とマージを削除し、バンドル フェーズにかかる時間を大幅に削減します。

6. バンドルのマージ時間を短縮するために、js、css、および swan テンプレートのコンパイルに対してビルド時のマークアップの最適化が行われました。

7. プレビュー段階とリリース段階での js 圧縮と難読化には、terser と esbuild の並列ソリューションが採用されており、esbuild はプレビュー パッケージを迅速に生成するために使用され、terser はリリース パッケージの圧縮率を確保できます。

結果から判断すると、新しいコンパイラは、古いバージョンに比べて速度、リソース使用量、保守性が大幅に向上しています。

03 新バージョンのコンパイラの仕組み

新しいコンパイラの処理の流れはパーセルと同様で、コンパイラが処理の流れを制御し、プロセッサがコード変換を行います。

いくつかの重要なモジュール:

  • CompileEntry コンパイラは、cli 通信、dev サーバー通信、コマンド呼び出しなどを含むエントリ モジュールです。

  • CompileManager はコンパイル マネージャーであり、依存リソースのダウンロードと管理、および複数のコンパイラーの共同ビルドに使用されます。

  • コンパイラは、プロジェクトのソース コードをランタイム コードにコンパイルするために使用されるコンパイラ モジュールです。プロジェクトをビルドするときに複数のコンパイラが存在する場合があります。

  • プロセッサは、コード変換やコードのマージなどの個々のコンパイル タスクを処理するために使用される単位プロセッサです。

: ミニ プログラム アプリ プロジェクトには 1 つのコンパイラーがあり、ダイナミック ライブラリとダイナミック拡張プロジェクトには 2 つのコンパイラーがあります。

3.1 コンパイラ コンパイラ

単一の小さなプログラム プロジェクトをコンパイルし、開発者の元のコードを実行可能なコードにコンパイルするために使用されます。

仕事の義務:

1. 実行コンテキストを作成し、プロセッサーが使用する構成、fs ファイル処理、ウォッチャー監視、ロガー、およびその他のモジュールを提供します。

2. ファイルが変更された場合の完全なコンパイルと 2 番目のコンパイル。ここでの 2 番目のコンパイルも完全なコンパイル プロセスを経ますが、そのほとんどはキャッシュされた結果を使用します。

3. プロセッサ処理装置を管理、スケジュール、および実行します。

4. プロセッサの依存関係と結果キャッシュを維持します。

特徴:

1. フルプロセス キャッシュを実装し、各プロセッサの入力パラメータと出力結果をキャッシュに書き込みます。キャッシュにより、2 次コンパイル時間を 90% 削減できます。

2. オンデマンド コンパイルのサポート オンデマンドのシングル ページ コンパイル、インクリメンタル コンパイル、およびフル コンパイルはすべて、同じプロセッサ処理フローに従います。

3. プロキシ メカニズムを通じてキャッシュ パラメーターの依存関係を自動的に計算するため、各プロセッサーのキャッシュ ハッシュを手動で生成する必要がなくなり、webpack やパーセルと比較してバグが軽減されます。

4. Processor の依存関係のみが維持され、ModuleGraph は維持されないため、処理プロセスが簡素化されます。

フルプロセス キャッシュに関しては、各パッケージャには独自の実装計画があります。基本原則は、現在の入力パラメータと依存関係に基づいて処理ユニットの一意のハッシュを生成することです。ハッシュが一貫していれば、結果も一貫しています。

Webpack と Parcel は ModuleGraph を維持するため、キャッシュの計算と再利用はより複雑になります。アプレット コンパイラは、プロセッサの入力パラメータと呼び出しの依存関係に基づいて計算のみを実行します。

3.2 プロセッサユニットプロセッサ

プロセッサーには次の特性があります。

1. 入力パラメータが一貫している場合、出力も一貫していることが保証され、完全なプロセッサ キャッシュを実現するために、入力と出力の両方が json にシリアル化可能である必要があります。

2. プロセッサーの URI はビルド ID です。単一のビルド中に ID が一貫している場合、処理結果も一貫しています。たとえば、app.js ファイルを処理する場合、URI は js:app.js になります。利点は、プロセッサ リソースの処理パスを統合できることです。

3. プロセッサは相互の呼び出しをサポートします。processWith は呼び出して実行を継続し、processWithResult は呼び出して結果が返されるのを待ちます。

: ここでの入力パラメータには、uri、app config、contextFreeData が含まれます。

一般的に使用されるいくつかのプロセッサ:

1.JS プロセッサは、es6 コードを es5 コードに変換します。これは最も時間のかかるモジュールです。

2.Swan Processor は、swan テンプレート コードをビュー レイヤー js コードに変換します。

3.Css プロセッサは postcss を使用して、CSS での単位変換、依存関係の収集、その他の作業を処理します。

4. バンドル プロセッサは、バンドル アルゴリズムに従って前のトランスフォーマによって処理されたファイルをマージし、結果を出力します。

プロセッサーのワークフロー:

Processorの処理の流れは、transform→bundleのプロセスを経る必要があり、ミニプログラムでは、webpackの処理モードとは異なり、js、css、swanテンプレートのバンドルを個別に並列処理することができ、小包のパイプラインに似ています。

3.3 パフォーマンスと製品の最適化方法

3.3.1 マルチコアコンパイルの最適化

Node のマルチスレッド モジュールの初期化速度と通信効率はマルチプロセスよりも優れているため、新しいコンパイルではマルチコアの最適化のためにマルチスレッドを使用することを選択します。

マルチスレッドコンパイルには 2 つのオプションがあります。

  • オプション 1: プロセッサベースのマルチスレッド スケジューリング プロセッサは相互呼び出しをサポートするため、実際の処理は非常に複雑になり、通信コストがかかります。

  • 古いコンパイラは webpack に基づいてワーカースレッド ローダーを作成しており、パフォーマンスの向上は限定的 (10% ~ 15%) でした。

  • Parcel は、lmdb パブリック キャッシュに基づいた、スレッド間の通信を排除し、読み取りと書き込みの効率を確保するためのより優れたソリューションです。

  • オプション 2: js 変換のマルチスレッド スケジューリングのみを実行し、通信コストは 2 つだけです。

  • js マルチスレッド変換には jest-worker と babel 変換を使用するか、js 変換には swc マルチスレッドを使用します。

ビルド時間のほとんどは js の変換に費やされるため (js には多くの node_modules 依存関係があり、そのすべてを変換する必要があります)、css および swan モジュールの変換にかかる時間は短くなります。

最後のオプション 2 は、js マルチスレッド変換のみを実行します。処理プロセスがシンプルになり、メリットが大きくなります。全体的な改善は次のとおりです。

  • jest-worker マルチスレッド Babel 変換を使用すると、4 つのスレッドにより速度が 1 倍以上向上します。

  • js 変換に swc を使用すると、4 つのスレッドにより速度が 4 倍以上向上します。

JS プロセッサのマルチスレッド:

で:

uri : プロセッサの ID を構築します

contextFreeData : 単一ビルド内の不変データ (app.json の構成項目など)

context args : 最適化実験スイッチ、マルチスレッド スイッチなどのグローバル パラメータ。

js変換処理ではtransformer統合変換インタフェースが規定されており、これに基づいてbabelシングルスレッド、babelマルチスレッド、swc変換の3種類のプロセッサが実装されており、いつでもプロセッサの切り替えが可能です。

さまざまなコンパイル環境に合わせて柔軟な設定を行うことができます。

1. 開発者ツールでは、開発者はマシン構成に応じてマルチスレッド コンパイル モードと swc コンパイル モードを切り替えて効率を向上できます。

2. クラウド コンパイル パイプラインは、パフォーマンスを向上させるために、デフォルトでマルチスレッド コンパイルを有効にします。

3. webIDE は、リソースの消費を削減するために、デフォルトで単一のスレッドを開きます。

3.3.2 SWC コンパイルの最適化

古いコンパイルと比較して、新しいコンパイラのマルチスレッド モードは約 1 倍改善されました。開発開発中、一部の大きなプロジェクト ページの最初のコンパイルはまだ少し遅く、主に js で 10 秒以上かかります。変身。

swc は基本的に js 翻訳において成熟しており、ほとんどのシナリオで翻訳速度を 4 倍以上向上させることができるため、swc マルチスレッド翻訳サポートが追加され、大きなプロジェクト ページの最初のコンパイルを 5 秒以内に制御できるようになりました。

swc 変換に適応するには、2 つの swc プラグインを作成する必要があります。

  • @swanide/swc-require-rename は、require/import/export 内のモジュールのパス情報を抽出し、その後の js でのモジュールの依存関係の分析を容易にします。

  • @swanide/swc-web-debug は、js コードでインストルメンテーション処理を実行し、実機デバッグでのブレークポイント デバッグをサポートします。

swc コンパイルによってパフォーマンスが大幅に向上しましたが、使用中にいくつかの問題も発見されました。

1. swc にメモリ リークがあり、開発段階でフル コンパイルの数が多すぎると、メモリ使用量が高くなり、コンパイラを手動で再起動する必要があります。

2. swc プラグインがサポートする API は少なく、babel で簡単に実装できる一部の関数は swc で処理するのが困難です。

3. swc は Rust を使用してプラグインを作成するため、異なる @swc/core バージョン間でプラグインを使用することはできず、異なるプラットフォーム用に swc プラグインを生成する必要があり、導入がより面倒になります。

実際の使用では、swc がうまく処理できない一部のシナリオでは、babel にダウングレードして処理されます。

3.3.3 コード圧縮と実行時キャッシュ

開発段階では、コンパイルされたコードは圧縮されず、シミュレーターで実行できます。プレビュー リリース段階ではパッケージ サイズに制限があるため、製品サイズを小さくするにはコード圧縮が必要です。

オプションのコード圧縮ツールが 3 つあります。

1.terser は圧縮率が高く、生成量は小さく、速度は最も遅いです。

2.swc は圧縮が速く、マングルのサポートが不完全で、圧縮率が低い。

3.esbuild は圧縮が最も速く (terser より 10 倍以上高速)、マングルをサポートしますが、コード圧縮率は terser ほど良くありません。

最終的に、比較と検討の結果、次の圧縮スキームが選択されました。

1. プレビュー段階ではソースマップが必要ないため、ソースマップを削除し、コード圧縮に esbuild を使用してプレビュー速度を向上させます (自動プレビュー シーンの大幅な改善)。

2. リリース段階でマルチスレッド圧縮に terser を使用し、ソースマップを保持します。

ランタイム キャッシュとは、プロセッサ処理結果やコード圧縮結果など、ビルド プロセスの中間結果がメモリにキャッシュされることを意味します。これにより、2 回目のビルド時のリビルド時間のほとんどを節約できます。文字列と json オブジェクトはキャッシュに保持されるため、webpack ベースの古いコンパイラーと比較して 40% ~ 60% のメモリ節約があり、メモリ使用量の観点からは許容範囲内です。

3.3.4 Swan テンプレート処理の最適化

古い swan テンプレート処理では、テンプレート変換に swan-loader が使用されます。テンプレートのインポート スコープが設計中に適切に処理されないため、<template> タグとフィルター フィルター関数はページ コードにインライン化することしかできません。テンプレートが広範囲で使用されている場合は、テンプレートとフィルターを使用すると、最終的に生成されるコード サイズは非常に大きくなります。

新しいコンパイラは、インポート スコープの関係を修正し、コンパイルされた製品のテンプレートとフィルターの生成モードをインラインから参照が必要に変更し、同じモジュールを再利用できるようにバンドル ステージでコードをマージします。これにより、大きな穴が埋められます。 。

新しいコンパイラ swan テンプレートの処理フロー:

単一の swan ファイルがプロセッサーによって処理された後に考えられるプロダクトは次のとおりです。

  • コンポーネント コンポーネント モジュール。ページとカスタム コンポーネントの生成に使用されます。

  • テンプレートモジュール

  • filterフィルター関数、sjsフィルター関数

  • 変換されたドキュメントの中間コード

swan テンプレートをさまざまなタイプの js モジュールに変換し、依存関係を維持して、後続のコードのマージ時により洗練された制御を容易にします。

歴史的な理由により、import/include に sjs またはテンプレート参照が含まれている場合、テンプレート モジュールを直接生成することはできず、最終的なエントリ テンプレートで生成する必要があります。新しいコンパイルでは、テンプレートの静的コンパイル オプションも提供されており、インポート範囲を厳密に制限し、テンプレート モジュール コードを直接生成します。taro によって生成された小規模なプログラム プロジェクトの場合、製品サイズの約 30% を節約できます。

3.3.5 ソースマップの最適化

コンパイラは JS コードのデバッグとランタイム エラーの追跡をサポートする必要があるため、開発フェーズとリリース フェーズの両方でソースマップを生成する必要があります。

Webpack でコードを生成する場合、ソースマップをマージして計算する必要があります。大規模なプロジェクトの場合、ソースマップのマージに時間がかかり、再コンパイルのたびにソースマップを再計算する必要があります。

調査中に、ブラウザの開発ツールがソースマップ プロトコルのインデックス マップを非常に適切にサポートしていることがわかりました。新しいコンパイラは、インデックス マップ プロトコルに基づいてソースマップのマージ最適化を実行しました。以前の複数ファイルのソースマップのマージ計算は、計算式になりました。オフセット マップを生成し、コンテンツを結合するこの方法 js バンドルにかかる時間が、数秒から数十秒から 3 秒未満の固定時間に変更されました。

興味深いのは、vscode の js-debugger は 6 月 22 日までインデックス マップのデバッグをサポートしておらず (インデックス マップは 2011 年にリリースされました)、Microsoft のアクションは若干遅かったことです。

3.3.6 フォローアップ作業

開発完了後の新しいコンパイラのプロモーションでは、プログレッシブ プロモーション方法が採用されます。

第 1 段階では、開発者ツールの新旧コンパイラが共存し、dev とプレビューは新しいコンパイラを使用し、リリースは古いコンパイラを使用します。

第 2 段階では、すべての内部パイプラインのプレビューとリリースで新しいコンパイルが使用されます。

第 3 段階では、すべての開発者ツールが新しいコンパイラに切り替えられます。

新しいバージョンがコンパイルされて実際にオンラインになった後も、いくつかの小さな互換性の問題がまだ残っているため、完全な置き換えがリリースされる前に、できるだけ早く問題を明らかにする必要があります。

小規模なプログラム プロジェクトの場合、新しいコンパイルによって多くの最適化作業が行われていますが、次のような一部の最適化作業はまだ完了していません。

HMR ホット リロード: 開発中、ランタイム フレームワークと開発者ツールはインターフェイスの適応を必要とするため、期待を達成するためにデバッグに長い時間がかかります。

ツリーを揺るがすコードの削除: es6 モジュールの場合、ツリーを揺るがすコードは変換段階で削除できます。

スコープホイスティング スコープホイスティング: 理論的には可能ですが、コード削減効果を検証する必要があります。

新しいバージョンのコンパイラは、古いバージョンのコンパイラのビルド結果と完全な互換性がある必要があるため、バンドル パッケージ化シナリオには最適化の余地がまだあり、ランタイムと組み合わせてパッケージ化された製品をさらに最適化できます。その後の作業のフレームワーク。

04 まとめ

新しいバージョンのコンパイラは、独自に開発したパッケージング ソリューションを採用しており、Webpack ベースの古いコンパイラと比較して、大幅なパフォーマンスの向上を実現し、コンパイルの遅さとリソースの使用量の多さの問題を完全に解決しました。競合他社のコンパイラ。

swc 変換、esbuild 圧縮、ソースマップ最適化など、新しいコンパイルによって導入された一部の最適化手法は、他のフロントエンド プロジェクトの構築にも使用でき、高速化効果があります。

新しいコンパイラ プロジェクトでは、学生全員が非常に熱心に働き、多くの素晴らしいアイデアを提供し、遭遇した問題のほとんどは効果的に解決されました。当社は今後もパフォーマンスと製品の最適化という 2 つの方向を堅持し、開発者のエクスペリエンスとランタイム効率を継続的に向上させていきます。

- 終わり -

推奨読書

Baidu APP iOS パッケージサイズ 50M の最適化実践 (6) 無駄なメソッドのクリーニング

異常なオンライン シナリオに基づくリアルタイムの傍受と問題分散戦略

極めて最適化された SSD 並列読み取りスケジューリング

AIテキスト作成と百度アプリでの公開の実践

DeeTune: eBPF に基づく Baidu ネットワーク フレームワークの設計と適用

200元の罰金と100万元以上を没収 You Yuxi: 高品質の中国語文書の重要性 MuskのJDK 21用ハードコア移行サーバー Solon、仮想スレッドは信じられないほど素晴らしい! TCP 輻輳制御によりインターネットが節約される OpenHarmony 用の Flutter が登場 Linux カーネルの LTS 期間が 6 年から 2 年に復元される Go 1.22 で for ループ変数エラーが修正される Svelte は「新しいホイール」を作成 - ルーン文字 Google が創立 25 周年を祝う
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4939618/blog/10114374
おすすめ