makeコマンドのコンパイルを「-j」オプションで高速化|並列ビルドと主要オプション一覧

HOMEリナックスマスター.JP 公式ブログLinux情報・技術・セキュリティ > makeコマンドのコンパイルを「-j」オプションで高速化|並列ビルドと主要オプション一覧
宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
「makeが終わらない。ApacheやMySQLをソースからビルドすると、毎回コーヒーを2杯飲めるくらい待たされる」

ソースコードからのビルドは、待ち時間がそのまま作業時間を食います。makeコマンドには、この待ち時間を削るための -jオプション(ジョブ数の指定)があります。マルチコアの環境なら、コンパイルを並列に走らせてビルドを高速化できます。

この記事では、「-j」オプションの使い方をCPUコア数の調べ方から順に解説します。定番の書き方である make -j$(nproc)、引数なしの make -j が危険な理由、並列ビルドが壊れた時の切り分け手順、そして「-j」以外の主要オプションの一覧までをまとめます。2013年に実機で測ったログもそのまま残してあります。

なお、Makefileの書き方や configure → make → make install の流れそのものは makeコマンドとMakefileの使い方|ソースコードのビルドからCMakeとの違いまで で解説しています。この記事は「ビルドを速くする側」に絞ります。

この記事のポイント

・make -j$(nproc) がCPUコア数に合わせた並列ビルドの定番
・引数なしの make -j はジョブ数無制限でメモリを食い潰す
・-l でロードアベレージに上限をかければ共用サーバーでも安全
・並列で失敗したら make -j1 に戻して依存関係を疑う

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

「-j」オプションでmakeが速くなる仕組み

makeはMakefileの依存関係を読み、どのファイルをどの順番でビルドするかを決めます。既定では、そのターゲットを1つずつ順番に処理します。

つまり、CPUコアが4つあってもmakeは1コアしか使いません。残り3コアは遊んだままです。

「-j」オプションは、この空きを埋めるためのものです。依存関係の上で「互いに待つ必要がないターゲット」を同時に走らせます。

-jオプションを付けたmakeは、空いているコアにもコンパイルを配ります。付けなければ、コアが何個あっても1つずつです。

# 書式(ジョブ数を4に指定する) make -j4 # 数字を離して書いても同じ意味になる make -j 4

ここで先に押さえておきたいのが、「-j」は万能ではないという点です。configureやリンク処理のように1本道の工程は並列にできません。並列化できる工程が少ないビルドでは、ジョブ数を増やしてもほとんど短くなりません。この記事の後半で、実測ログを使ってその差を示します。

CPUコア数を確認してジョブ数を決める

ジョブ数は、そのマシンのCPUコア数を基準に決めます。確認方法は3つあります。

1. nprocで論理CPU数を確認する

一番手軽なのが nproc(coreutils付属)です。

# nproc で、今のプロセスが使える論理CPU数を表示する nproc 4 # インストールされている全CPU数を見る nproc --all 4

nproc は「今のプロセスが使えるCPU数」を返します。taskset や cpuset(使うCPUを番号で限定するcgroupの機能)で割り当てが絞られている環境では、nproc と nproc --all の値がずれます。

注意: ずれるのはCPUの番号を限定した場合だけです。コンテナでよく使うCPU時間の割り当て(dockerの --cpus=2、cgroupの cpu.max)では、nproc の値は変わりません。使えるCPU時間の総量が絞られるだけで、見えるCPUの数はホストのままだからです。--cpus=2 で起動したコンテナの中でも nproc はホストのコア数を返すので、make -j$(nproc) と書くと割り当ての何倍ものジョブが立ち上がります。コンテナの中でビルドする時は、割り当てた値を自分でジョブ数に書いてください。

2. lscpuで物理コアとハイパースレッドを見分ける

nproc が返すのは論理CPU数です。ハイパースレッディング(1つの物理コアを2つに見せる機能)が有効なマシンでは、物理コア数の2倍の値になります。内訳を見るには lscpu を使います。

# lscpu でCPUの構成を確認する(4コアのARM機での実行例) lscpu Architecture: aarch64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Vendor ID: ARM Model name: Cortex-A76 Thread(s) per core: 1

「CPU(s)」が論理CPU数、「Thread(s) per core」が1コアあたりのスレッド数です。上の例では 1 なので、4コアがそのまま4論理CPUです。ハイパースレッディングが有効なx86サーバーでは、ここが 2 になります。BIOSで無効にして運用しているサーバーもあるので、値は実機で確認してください。x86では「Core(s) per socket」「Socket(s)」の行も出るため、物理コア数はそちらで数えられます。

3. getconf _NPROCESSORS_ONLN でも確認できる

nproc が入っていない最小構成のコンテナや、古い環境ではこちらが使えます。

# getconf でオンラインのCPU数を得る getconf _NPROCESSORS_ONLN 4

ただし getconf は taskset や cpuset の指定を見ません。nproc --all と同じで、そのマシンの全コア数をそのまま返します。使えるCPUを絞った環境でジョブ数を決める時は、nproc のほうを使ってください。

4. make -j$(nproc) を定番の書き方にする

コア数を毎回目で確認して打ち直す必要はありません。コマンド置換でそのまま渡します。

# CPUコア数をそのままジョブ数にする(最もよく使う書き方) make -j$(nproc) # ファイルの読み書き待ちを埋めるため、1つ多く走らせる make -j$(($(nproc)+1))

どちらでも構いません。コンパイルにはファイルの読み書きで待つ時間があるため、コア数+1にすると、その待ち時間の分だけ隙間が埋まります。メモリに余裕がないマシンでは、素直に make -j$(nproc) にしておくほうが安全です。

【重要】引数なしの make -j は使わない

「-j」の後ろに数字を書かないと、GNU makeはジョブ数を制限しません。依存関係が許す限り、無制限にコンパイラを起動します。大きなプロジェクトでは数百プロセスが同時に立ち上がり、メモリを食い潰してサーバーが応答しなくなります。

やっかいなのは、数字を書いたつもりで書けていないケースです。

# 危険: all はジョブ数として解釈されないため、ジョブ数が無制限になる make -j all

実際に本番サーバー(Amazon Linux 2 / GNU Make 3.82)で、makeが受け取ったフラグを出力するだけのMakefileを作って確認しました。

# make -j4 all の場合(ジョブ数の上限あり。jobserverが有効) MAKEFLAGS=[ --jobserver-fds=3,4 -j] # make -j 4 all の場合(同じく上限あり) MAKEFLAGS=[ --jobserver-fds=3,4 -j] # make -j all の場合(jobserverが無く、上限なし。all はターゲット扱い) MAKEFLAGS=[j]

数字を離して書いた make -j 4 all は正しく制限されます。危ないのは数字そのものを書き忘れた時です。ジョブ数は必ず明示してください。

2013年に実機で測ったmake -jの効果

以下は2013年に測定した実測ログです。当時の環境は次のとおりで、いずれも現在はサポートが終了しています。

・VMware Player 5 上の仮想マシン
・CentOS 6.4 / メモリ 1GB / CPUコア数 4
・対象ソフト: Apache 2.2.24 と MySQL 5.6.10

数字自体は古い環境のものです。ただし-jオプションが効く場合と効かない場合の差がそのまま出ているので、判断材料として残します。処理時間の測定には time コマンドを使いました。

1. Apache 2.2.24の結果(ほとんど速くならなかった)

[root@Tiger httpd-2.2.24]# time make real 5m2.725s user 0m49.494s sys 5m14.875s [root@Tiger httpd-2.2.24]# time make -j2 real 4m37.823s user 1m21.068s sys 8m40.777s [root@Tiger httpd-2.2.24]# time make -j3 real 4m28.039s user 2m9.022s sys 10m57.001s [root@Tiger httpd-2.2.24]# time make -j4 real 4m34.442s user 2m41.338s sys 12m47.424s [root@Tiger httpd-2.2.24]# time make -j5 real 4m28.378s user 2m35.472s sys 12m35.351s [root@Tiger httpd-2.2.24]# time make -j6 real 4m32.672s user 2m35.304s sys 12m52.230s [root@Tiger httpd-2.2.24]# time make -j7 real 4m29.440s user 2m30.057s sys 12m41.908s [root@Tiger httpd-2.2.24]# time make -j8 real 4m32.804s user 2m31.532s sys 12m57.533s

一番速かったのが -j3 の 4分28秒で、-jなしの5分3秒から約11%の短縮にとどまりました。-j4以降は横ばいです。

気になるのが sys 時間です。5分15秒から12分58秒へ、2.5倍に膨らんでいます。並列にした分だけカーネル側の処理が増えているのに、実時間はほとんど縮んでいません。

2. MySQL 5.6.10の結果(36%短縮した)

同じ仮想マシンで、MySQL 5.6.10をビルドした結果です。

[root@Tiger mysql-5.6.10]# time make real 25m20.778s user 18m35.102s sys 5m7.402s [root@Tiger mysql-5.6.10]# time make -j8 real 16m13.095s user 33m56.244s sys 14m29.432s

25分21秒が16分13秒になりました。約36%の短縮、倍率にして1.56倍です。同じマシン、同じジョブ数なのに、Apacheとはまるで違う結果になりました。

3. なぜ同じ環境で結果が食い違ったのか

答えは time コマンドの user 欄に出ています。
対象(-jなし) real(実時間) user(CPUでの計算時間) 読み取れること
Apache 2.2.24 5m2.725s 0m49.494s CPUをほとんど使っていない
MySQL 5.6.10 25m20.778s 18m35.102s CPUを使い切っている
Apacheのビルドは、5分かかっているのに user 時間が49秒しかありません。コンパイラが計算していた時間は1分未満で、残りはlibtoolのシェルスクリプト起動、ファイルの読み書き、仮想マシンのI/O処理に消えています。並列にしたくても、並列化できるCPU作業がそもそも足りません。だから約11%しか縮まず、増えたのはカーネル側の sys 時間だけでした。

一方のMySQLは user 時間が18分35秒あり、real の73%を占めます。C++のコンパイルでCPUを使い切っている状態なので、ジョブを増やせばその分だけ実時間が縮みます。

ただしMySQLでも1.56倍止まりでした。4コアあるのに4倍にならなかった最大の理由は、メモリ1GBという構成にあります。C++のコンパイラ(cc1plus)はソースによっては数百MB単位でメモリを使うため、8ジョブを同時に走らせるとメモリが足りません。実際、-j8では user 時間そのものが18分35秒から33分56秒へ増えています。user と sys を足した合計CPU時間で見ると23分42秒から48分25秒へ、2.04倍です。同じビルドなのに合計CPU時間が増えるのは、キャッシュの奪い合いで1ジョブあたりの効率が落ちたと読める動きです。

ただし、この読み方だけでは説明しきれません。スワップが主因なら増分は sys 側に強く出るはずですが、実際は user が1.83倍に増えています。当時残した記録は time の出力だけなので、この増加の原因は特定できていません。それでも「4コアあっても4倍にはならない」「メモリが足りないマシンで並列度を上げると割に合わなくなる」という結論そのものは、real の値で確認できます。

【判断の目安】 time make の結果で user が real に近ければ「-j」は効きます。user が real よりはるかに小さければ、CPUはボトルネックではありません。ジョブ数を増やす前に、ディスクI/Oとメモリを疑ってください。

メモリに余裕がある現代のマシンなら、ここまでの頭打ちにはなりません。それでも、リンク処理やconfigureのように並列化できない工程は必ず残るため、コア数と同じ倍率にはならない点は今も変わりません。

-lでロードアベレージに上限をかける

自分専用のマシンなら make -j$(nproc) で問題ありません。ですが、他の人も使っているサーバーや、本番サービスが動いているマシンでこれをやると、ロードアベレージが跳ね上がって他の処理が止まります。

-l(--load-average)は、ロードアベレージが指定値以上のあいだ、新しいジョブを起こさなくするオプションです。指定値と同じ値になった時点で待ちに入る点に注意してください。

# ロードアベレージが4以上のあいだは新しいジョブを起こさない make -j8 -l4 # コア数に合わせて自動で決める make -j$(nproc) -l$(nproc)

-l は -j と併用してはじめて意味を持ちます。単独で書いても並列にはなりません。

注意: -l は「起動済みのジョブを止める」わけではありません。指定値に達した時に「次の1本」を待たせるだけです。したがって負荷は一時的に指定値を超えます。本番サーバー上でのビルドは、そもそも避けるのが原則です。

--output-syncで並列ビルドのログの混ざりを止める

並列ビルドで一番困るのが、複数のコンパイラの出力が1行ずつ混ざり合い、どのファイルのエラーなのか読めなくなることです。

GNU make 4.0(2013年10月リリース)で追加された --output-sync(短縮形 -O)を使うと、出力をターゲット単位でまとめてから表示します。

# ターゲット単位で出力をまとめる make -j$(nproc) --output-sync=target # 短縮形でも同じ意味になる make -j$(nproc) -Otarget

指定できる種類は none(従来どおり混ざる)、line(1行単位)、target(ターゲット単位)、recurse(再帰呼び出し単位)の4つです。-O だけを書いた場合は target と同じ扱いになります。

注意: 使う前に make --version でバージョンを確認してください。3.x系にはこのオプションがありません。

# make --version でバージョンを確認する(Amazon Linux 2 での実行例) make --version GNU Make 3.82 Built for x86_64-koji-linux-gnu

実際、運用中のAmazon Linux 2のサーバーで確認したところ make 3.82 で、--output-sync は使えませんでした。CentOS 7も3.82です。RHEL 8以降やUbuntu 20.04以降であれば4.x系が入っています。

-jと組み合わせて使うmakeコマンドの主要オプション一覧

並列ビルドを回す時に、-jオプションと組み合わせて使うものに絞ってまとめます。
オプション 意味 使用例
-j N N個のジョブを並列に実行する make -j4
-j1 並列をやめて1ジョブで実行する(切り分け用) make -j1
-l N ロードアベレージがN以上のあいだは新しいジョブを起こさない make -j8 -l4
-O / --output-sync 並列時の出力をまとめて表示する(GNU make 4.0以降) make -j8 --output-sync=target
--debug=j ジョブの起動状況をデバッグ出力する(並列の調査用) make --debug=j -j4
-C ディレクトリ 指定ディレクトリへ移動してから実行する make -C /usr/local/src/httpd-2.4.65 -j4
-w ディレクトリの出入りを表示する(並列時にどこで落ちたか追う) make -w -j4
-k エラーが出ても可能な限り続行する(原因調査中は外す) make -k -j4
-i コマンドのエラーそのものを無視して進む make -i
-k と -i は似ていますが役割が違います。-k は失敗したターゲット以外のビルドを続ける指定で、失敗そのものは最後に報告されます。-i はコマンドのエラーを無視して成功扱いにするので、壊れた成果物ができあがる可能性があります。実務で使うのはほぼ -k のほうです。

ここに挙げていないオプション(-n のドライラン、-s のサイレント、-B の全ターゲット再ビルド、-f での Makefile 指定など)は、並列ビルドとは直接関係しない単体の機能です。それぞれの意味と使用例は makeコマンドとMakefileの使い方|ソースコードのビルドからCMakeとの違いまで の「makeコマンドのよく使うオプション」にまとめてあります。

並列ビルドが失敗した時のトラブルシュート

「-j」を付けた途端にビルドが通らなくなることがあります。順番に切り分けます。

1. まず make -j1 に戻して切り分ける

最初にやることはこれです。

# 並列をやめて1ジョブで実行し、並列が原因かどうかを確かめる make clean make -j1

-j1で通り、-j4で落ちるなら、原因はMakefileの依存関係の書き漏れです。並列だと、まだ生成されていないファイルを別のジョブが先に読みに行ってしまいます。

このタイプの失敗は「毎回同じ場所で落ちるとは限らない」のが特徴です。実行するたびにエラーの出るファイルが変わるなら、ほぼ依存関係の記述漏れだと考えて構いません。

自分で管理しているMakefileなら、依存関係を正しく書き足すのが本筋です。他人のソースで手を入れにくい場合は、Makefileの先頭に .NOTPARALLEL: を書くと、そのMakefileの中だけ並列実行を無効にできます。

# makefileの先頭に書くと、このMakefile内では並列実行しない .NOTPARALLEL:

注意: 上流のソースを書き換えると、次のバージョンアップのたびに同じ作業をやり直すことになります。運用の手順書に「このソフトは -j1 でビルドする」と書いてしまうほうが、結局は確実です。

2. cc1plusがKilledで落ちる場合(メモリ不足)

c++: internal compiler error: Killed (program cc1plus) というメッセージで止まる場合、コンパイラが自分で異常終了したわけではありません。カーネルのOOM Killer(メモリ不足時にプロセスを強制終了する仕組み)に殺されています。

# dmesg にOOM Killerの記録が残っていないか確認する dmesg | grep -i "killed process" # systemd環境なら journalctl でも確認できる journalctl -k | grep -i "out of memory"

対処は「ジョブ数を減らす」か「スワップを足す」の2つです。C++のビルドでは1ジョブあたり1~2GBのメモリを見込んでください。メモリ4GBのマシンで -j8 は無理があります。

# メモリが足りない時はジョブ数を落とす make -j2

前半のMySQLの実測で1.56倍にとどまったのも、根はこれと同じです。具体的な症状と対処の手順は MySQLコンパイル時にc++: internal compiler error: Killed (program cc1plus)が発生した時の対処法 にまとめてあります。

3. ログが混ざって原因の箇所が読めない場合

GNU make 4.0以降なら --output-sync=target を付けます。使えない3.x系では、いったん1ジョブに戻して流し直すのが早道です。

# 原因調査のときは1ジョブに戻し、ログをファイルにも残す make -j1 2>&1 | tee build.log

注意: 原因調査中は -k(--keep-going)を外してください。付けたままだとエラーが出ても止まらないため、最初のエラーがログの奥深くに埋もれて見つけにくくなります。

ccache・distcc・ninjaでさらに短縮する

「-j」で頭打ちになったら、次の3つが選択肢になります。

1. ccache(同じコンパイルを2度やらない)

一度コンパイルした結果をキャッシュし、同じソースなら再利用します。configureのオプションを変えて何度もビルドし直す場面で効きます。

# ccache経由でコンパイルする make CC="ccache gcc" CXX="ccache g++" -j$(nproc) # キャッシュのヒット率を確認する ccache -s

2. distcc(他のマシンにコンパイルを配る)

ネットワーク上の複数マシンにコンパイル処理を分散します。手元のマシンのコア数を超えて並列度を上げられるのが利点です。ただしプリプロセスとリンクは手元で実行されるため、その分は短くなりません。ネットワークが遅い環境では、かえって不利になることもあります。

3. ninja(makeそのものを置き換える)

CMakeを使うプロジェクトなら、生成するビルドファイルをMakefileからNinjaに変えられます。

# cmakeでNinja向けのビルドファイルを生成して実行する cmake -G Ninja .. ninja

Ninjaは何も指定しなければCPUコア数に応じたジョブ数で並列実行します。依存関係の解決が速いため、2回目以降の差分ビルドで違いが出ます。

本記事のまとめ

やりたいこと コマンド
CPUコア数を調べる nproc
コア数ぶん並列でビルドする make -j$(nproc)
コア数+1で並列ビルドする make -j$(($(nproc)+1))
負荷に上限をかけて並列ビルドする make -j$(nproc) -l4
並列時のログの混ざりを止める make -j$(nproc) --output-sync=target
並列ビルドの失敗を切り分ける make -j1
原因調査でログをファイルにも残す make -j1 2>&1 | tee build.log
makeのバージョンを確認する make --version
メモリ不足で落ちていないか確認する dmesg | grep -i "killed process"
最後に、-jオプションを使う時の判断の順序をもう一度整理します。まず make -j$(nproc) を付けて測る。速くならなければ time の user を見る。user が小さければCPUはボトルネックではないので、ジョブ数を増やしても意味がありません。落ちるようになったら -j1 に戻して依存関係を疑う。makeコマンドの高速化は、この3手で現場に出てくる場面のほとんどが片付きます。

makeコマンドとMakefileの使い方|ソースコードのビルドからCMakeとの違いまで(Makefileの書き方とconfigureからの流れ)
MySQLコンパイル時にc++: internal compiler error: Killed (program cc1plus)が発生した時の対処法(メモリ不足でビルドが落ちる時)
Linux情報・技術・セキュリティの記事一覧

<PR>手元に置いて学びを深める1冊

GNU Make 第3版

makeコマンドの並列ビルドから高度なMakefileの書き方まで、GNU Makeの仕組みを体系的に学べるO'Reilly定番の1冊。

ソースからのビルドで手が止まらないサーバー管理者になりませんか?

並列ビルドで待ち時間を削れても、configureのオプション選びやライブラリの依存解決でつまずけば、そこで半日が消えます。手順をコピペするのではなく、なぜそのオプションが必要なのかを自分の言葉で説明できる状態を目指してください。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。