背景
prettier とは
prettier はソースコードを整形するフォーマッターです。
ごく少ない設定項目しか持たないため、事前準備をほとんどすることなく使い始めることができます。
prettierは複数のプログラミング言語を整形することができます。
今回はprettierでJavaScriptのソースコードをフォーマットします。
prettierの並列化
500ファイルのJavaScriptのソースコードをバンドルする際に
prettier --check
コマンドを実行して、フォーマット漏れが無いか確認しています。
実行すると3.33秒掛かかります。
~ time npx prettier --check 'src/lib/**/*.js'
All matched files use Prettier code style!
________________________________________________________
Executed in 3.33 secs fish external
usr time 4.92 secs 125.00 micros 4.92 secs
sys time 0.20 secs 875.00 micros 0.20 secs
これをもう少し速くしたいです。
prettier
はファイル単位で、フォーマットをチェックします。
ファイル間の依存関係がないため、ファイル単位での並列化は容易なはずです。
現にparallel-prettierという、prettierを並列実行するラッパーコマンドが存在します。
今どきの開発用PCのCPUはマルチコアです。
依存関係のないタスクを並列化すれば、コア数が許す限り理想値に近い性能向上が見込めそうです。
4コアのCPUで4並列化すれば、4倍の早さ、0.8
秒でチェックが完了するのではないでしょうか?
解決したい課題
parallel-prettierの性能は?
さっそくparallel-prettier
を使ってみましょう。
~ time npx pprettier --check 'src/lib/**/*.js'
bundle.js
modules/jquery.jsPlumb-1.5.5-min.js
modules/jquery.jsPlumb-1.5.5.js
✖ 3 files were not formatted
________________________________________________________
Executed in 9.18 secs fish external
usr time 488.60 millis 126.00 micros 488.48 millis
sys time 107.34 millis 903.00 micros 106.44 millis
実行時間が9秒に伸びました!?
parallel-prettierは.prettierignore
を無視
原因はparallel-prettierが.prettierignore
を無視しているためです。
対象ディレクトリにはいくつかのバンドル済みライブラリが配置されています。
それらのファイルは.prettierignore
ファイルに記載して、prettierの対象から外しています。
.prettierignore
を削除して、prettierを実行すると
~ time npx prettier --check 'src/lib/**/*.js'
Checking formatting...
[warn] src/lib/bundle.js
[warn] src/lib/modules/jquery.jsPlumb-1.5.5.js
[warn] Code style issues found in the above file(s). Forgot to run Prettier?
________________________________________________________
Executed in 9.02 secs fish external
usr time 12.89 secs 111.00 micros 12.89 secs
sys time 0.50 secs 834.00 micros 0.50 secs
実行時間は9秒に伸び、チェックに失敗します。
parallel-prettierとほぼ同じ結果になります。
これらのライブラリをparallel-prettierのチェック対象から除外しなくては、高速化できません。
また、チェックに失敗するので、本来の目的であるフォーマット済みの検証ができなくなります。
parallel-prettierが.prettierignore
を無視するのを解決する
prettierのAPIにはprettier.getFileInfoがあります。
これを使うと.prettierignore
ファイルに記載されているかどうか判定できます。
次のように使います。
const { ignored } = await prettier.getFileInfo(file.path, {
ignorePath: './.prettierignore',
});
これをparallel-prettierに組み込んだものが
です。
結果
上記ブランチをチェックアウトしてnpm run build
を実行するとdist/src/index.js
ができます。
~ time ~/parallel-prettier/dist/src/index.js --check 'src/lib/**/*.js'
✔ Checked 554 files
________________________________________________________
Executed in 2.67 secs fish external
usr time 422.47 millis 133.00 micros 422.34 millis
sys time 85.01 millis 869.00 micros 84.14 millis
2.67秒に縮まりました。やったね1.25倍速くなりました!
「えーっ、2倍にもならないのかよ・・・」がっかりです。
参考に、prettierignore
に記載しているライブラリを削除して、本家parallel-prettierを使ってみます。
~ time npx pprettier --check 'src/lib/**/*.js'
✔ Checked 551 files
________________________________________________________
Executed in 2.81 secs fish external
usr time 412.89 millis 127.00 micros 412.76 millis
sys time 84.67 millis 962.00 micros 83.70 millis
2.81秒です。prettierignore
を参照したことで遅くなったわけではありません。
今回のファイル群は、並列化してもあまり速くならないようです。
考察
今回の事例ではparallel-prettier並列化してもあまり速くなりませんでした。
並列化して速くならないことは、よくあることです。
しかし、parallel-prettierのREADMEには、16〜22倍高速化されたと書いてあります。
これは納得いきません。
原因の候補を考えてみます。
prettierのフォーマット処理はCPUバウンドでない?
フォーマット対象ファイルの読み込みがボトルネック?
parallel-prettierはファイル読み込みもワーカーに分散して、並列化しています。PCのIOの限界に到達するまで高速化するはず。
PCのIOが限界に到達するか調べる方法がわかりません。
メモリ不足で仮想ディスクを使っている?
メモリ余ってます。
ファイル単位の並列化ではボトルネックが解消されない?
特定のファイルのフォーマットがボトルネック?
否定する決め手はありません。
ファイル単位のフォーマット実行速度を計測すると良さそうに思います。
ファイルのフォーマット処理より、ファイル収集処理がボトルネック?
printfデバッグした感じ、フォーマット処理が始まるまでに秒単位の時間はかかっていません。
prettierが十分高速?
prettierはすでに、並列化されていて、さらに並列化しても速くならない?
現象としては魅的な理由です。
例えば、prettier実行時、nodeプロセスのCPU使用率が100%を超えています。
もっともらしく思えます。
単に、Node.js自体は並列化され(マルチスレッドで動い)ているからです。
Feature Request: Parallel/Clustered Prettier · Issue #4980 · prettier/prettierがopen中なのと矛盾しています。
また、prettierのソースコードを検索しても、cluster
、child_process
、spawn
、worker-thread
、cpu
などのそれらしい単語は含まれていません。
並列化しているとは思えません。
parallel-prettierの使っているprettierのバージョンが古い?
最新バージョン2.2.1
を使ってもparallel-prettierの実行時間に変化はありませんでした。
マルチプロセスによる並列化ではV8のJITコンパイラの効きが悪い?
- parallel-prettierのREADMEに載っているスコアと矛盾しています。
- Node.jsのCluster APIの存在とも矛盾します。
過去の経験上からもなさそうな気はします。
sosukesuzuki/prettier-parallel: Runs Prettier with Worker Threads はWorker Threadを使ってprettierを並列化しています。
parallel-prettierとprettier-parallelの性能を比較すると何かわかるかもしれません。
prettier-parallelはフォーマット対象のファイルが決め打ちです。
試すには何かしら修正が必要です。
オチ
実のところwebpackの実行に8秒かかっているため、prettierの3秒は、バンドル手順全体からみるとボトルネックではありません。