GNU Parallel で clang-tidy を 3 倍速くした

はじめに

42 Tokyo で C++ を使うチーム開発に取り組むにあたって、静的解析ツールの clang-tidy を導入しました。しかし、ファイル数があまり多くないにもかかわらず実行に時間がかかりすぎるという問題がありました。 そこで、GNU Parallel というツールを使って並列化することで、clang-tidy の実行時間を大幅に短縮することに成功しました。

この記事では、具体的な事例とその解決に役立った GNU Parallel について紹介します。

clang-tidy 遅すぎ問題

約 50 ファイル程度の lint に 1 分強かかっていました。遅いです。

CIの実行結果

clang-tidy がプロジェクト外のシステムヘッダーも読み込んでいることに原因がありそうです。

$ clang-tidy -p build $(git ls-files '*.cpp' '*.hpp')
...
537583 warnings generated.
549125 warnings generated.
Suppressed 549766 warnings (549113 in non-user code, 641 NOLINT, 12 with check filters).

根本的な原因を特定できればよかったのですが、あまり時間をかけたくなかったため、次に説明する GNU Parallel を使うことにしました。

GNU Parallel とは

GNU Parallel - GNU Project - Free Software Foundation

GNU Parallel はコマンドやスクリプトを並列実行するためのユーティリティです。複数のCPUコアを活用することで、時間のかかる処理を高速化できます。

GNU Parallel の使い方

公式ページにチートシートユースケースごとの豊富な使用例が載っています。ぜひ眺めてみてください。

ここでは基本的な使い方を紹介します。

$ parallel --version
GNU parallel 20240222

$ parallel -h
Usage:

parallel [options] [command [arguments]] < list_of_arguments
parallel [options] [command [arguments]] (::: arguments|:::: argfile(s))...
cat ... | parallel --pipe [options] [command [arguments]]

一番上が最も分かりやすい使い方です。標準入力をコマンドの引数に渡し、並列実行します。 この例だと、対象の HTML ファイルを並列に gzip 圧縮します。

find . -name '*.html' | parallel gzip --best

2つ目はクセがありますが、1つ目と似ています。こちらは標準入力ではなく、コマンドラインでジョブに分配する引数を渡します。以下のコマンドラインは上の例と同じ結果になります。

parallel gzip --best ::: *.html

3つ目は標準入力から受け取ったデータを、各ジョブの標準入力に分配する使い方です。デフォルトでは 1 MB のブロックに分割されます。ただし、分割は行単位で行われるため、ブロックサイズちょうどで分割されるわけではありません。

以下のコマンドの結果、ブロックごとの wc の結果が表示されます。

cat num1000000 | parallel --pipe wc

GNU Parallel で clang-tidy を並列化する

本題ですが、GNU Parallel の使い方が分かれば簡単です。以下のコマンドで clang-tidy を実行するようにしました。

git ls-files '*.cpp' '*.hpp' | parallel clang-tidy -p build

実行時間が 1/3 くらいになりました 👏

並列化後のCI

おわりに

GNU Parallel はシンプルかつ強力な並列処理ツールです。ぜひ使ってみてください。