この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
ソースコードからのビルドは、待ち時間がそのまま作業時間を食います。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 に戻して依存関係を疑う
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
「-j」オプションでmakeが速くなる仕組み
makeはMakefileの依存関係を読み、どのファイルをどの順番でビルドするかを決めます。既定では、そのターゲットを1つずつ順番に処理します。つまり、CPUコアが4つあってもmakeは1コアしか使いません。残り3コアは遊んだままです。
「-j」オプションは、この空きを埋めるためのものです。依存関係の上で「互いに待つ必要がないターゲット」を同時に走らせます。
-jオプションを付けたmakeは、空いているコアにもコンパイルを配ります。付けなければ、コアが何個あっても1つずつです。
# 書式(ジョブ数を4に指定する) make -j4 # 数字を離して書いても同じ意味になる make -j 4
CPUコア数を確認してジョブ数を決める
ジョブ数は、そのマシンのCPUコア数を基準に決めます。確認方法は3つあります。1. nprocで論理CPU数を確認する
一番手軽なのが nproc(coreutils付属)です。# nproc で、今のプロセスが使える論理CPU数を表示する nproc 4 # インストールされている全CPU数を見る nproc --all 4
注意: ずれるのは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
3. getconf _NPROCESSORS_ONLN でも確認できる
nproc が入っていない最小構成のコンテナや、古い環境ではこちらが使えます。# getconf でオンラインのCPU数を得る getconf _NPROCESSORS_ONLN 4
4. make -j$(nproc) を定番の書き方にする
コア数を毎回目で確認して打ち直す必要はありません。コマンド置換でそのまま渡します。# CPUコア数をそのままジョブ数にする(最もよく使う書き方) make -j$(nproc) # ファイルの読み書き待ちを埋めるため、1つ多く走らせる make -j$(($(nproc)+1))
make -j$(nproc) にしておくほうが安全です。【重要】引数なしの make -j は使わない
「-j」の後ろに数字を書かないと、GNU makeはジョブ数を制限しません。依存関係が許す限り、無制限にコンパイラを起動します。大きなプロジェクトでは数百プロセスが同時に立ち上がり、メモリを食い潰してサーバーが応答しなくなります。やっかいなのは、数字を書いたつもりで書けていないケースです。
# 危険: all はジョブ数として解釈されないため、ジョブ数が無制限になる make -j all
# 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
気になるのが 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
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を使い切っている |
一方の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 は「起動済みのジョブを止める」わけではありません。指定値に達した時に「次の1本」を待たせるだけです。したがって負荷は一時的に指定値を超えます。本番サーバー上でのビルドは、そもそも避けるのが原則です。
--output-syncで並列ビルドのログの混ざりを止める
並列ビルドで一番困るのが、複数のコンパイラの出力が1行ずつ混ざり合い、どのファイルのエラーなのか読めなくなることです。GNU make 4.0(2013年10月リリース)で追加された --output-sync(短縮形 -O)を使うと、出力をターゲット単位でまとめてから表示します。
# ターゲット単位で出力をまとめる make -j$(nproc) --output-sync=target # 短縮形でも同じ意味になる make -j$(nproc) -Otarget
注意: 使う前に make --version でバージョンを確認してください。3.x系にはこのオプションがありません。
# make --version でバージョンを確認する(Amazon Linux 2 での実行例) make --version GNU Make 3.82 Built for x86_64-koji-linux-gnu
-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 |
ここに挙げていないオプション(-n のドライラン、-s のサイレント、-B の全ターゲット再ビルド、-f での Makefile 指定など)は、並列ビルドとは直接関係しない単体の機能です。それぞれの意味と使用例は makeコマンドとMakefileの使い方|ソースコードのビルドからCMakeとの違いまで の「makeコマンドのよく使うオプション」にまとめてあります。
並列ビルドが失敗した時のトラブルシュート
「-j」を付けた途端にビルドが通らなくなることがあります。順番に切り分けます。1. まず make -j1 に戻して切り分ける
最初にやることはこれです。# 並列をやめて1ジョブで実行し、並列が原因かどうかを確かめる make clean make -j1
このタイプの失敗は「毎回同じ場所で落ちるとは限らない」のが特徴です。実行するたびにエラーの出るファイルが変わるなら、ほぼ依存関係の記述漏れだと考えて構いません。
自分で管理しているMakefileなら、依存関係を正しく書き足すのが本筋です。他人のソースで手を入れにくい場合は、Makefileの先頭に .NOTPARALLEL: を書くと、そのMakefileの中だけ並列実行を無効にできます。
# makefileの先頭に書くと、このMakefile内では並列実行しない .NOTPARALLEL:
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"
# メモリが足りない時はジョブ数を落とす make -j2
3. ログが混ざって原因の箇所が読めない場合
GNU make 4.0以降なら --output-sync=target を付けます。使えない3.x系では、いったん1ジョブに戻して流し直すのが早道です。# 原因調査のときは1ジョブに戻し、ログをファイルにも残す make -j1 2>&1 | tee build.log
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
本記事のまとめ
| やりたいこと | コマンド |
|---|---|
| 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" |
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冊
makeコマンドの並列ビルドから高度なMakefileの書き方まで、GNU Makeの仕組みを体系的に学べるO'Reilly定番の1冊。
ソースからのビルドで手が止まらないサーバー管理者になりませんか?
並列ビルドで待ち時間を削れても、configureのオプション選びやライブラリの依存解決でつまずけば、そこで半日が消えます。手順をコピペするのではなく、なぜそのオプションが必要なのかを自分の言葉で説明できる状態を目指してください。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:CentOS6.4をLinuxカーネル3.10.0にアップデートしてみました(カーネル再構築)
- 前のページへ:CentOS6.4がリリースされました。
- この記事の属するカテゴリ:Linux情報・技術・セキュリティへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます