Parallel Build Basics
Part 11 of 16 — GNU Make Mastery Series. A project with 200 source files compiled serially might take 60 seconds. With make -j8 on an 8-core machine, the same build can finish in under 10 seconds. Parallelism in Make is powerful — but it exposes hidden dependency bugs that serial builds silently tolerate.
1
Build Systems Foundations
Why Make, compilation pipeline, basics
2
Targets, Prerequisites & Execution
Rule anatomy, PHONY, build workflows
3
Variables, Expansion & Scope
= vs :=, ?=, +=, CLI overrides
4
Automatic Variables & Pattern Rules
$@, $<, $^, $?, %.o: %.c patterns
5
Built-in Functions & Make Language
subst, wildcard, foreach, $(shell)
6
Conditionals & Configurable Builds
ifeq, ifdef, platform detection, flags
7
Automatic Dependency Generation
-M, -MM, -MD, .d files, header deps
8
Compilation Workflow & Libraries
Static/shared libs, ar, -fPIC, SONAME
9
Project Architecture & Multi-Directory
Recursive make, include, out-of-source
10
Cross-Compilation & Toolchains
Toolchain prefixes, sysroots, embedded
11
Parallel Builds & Performance
make -j, jobserver, race conditions
You Are Here
12
Testing, Coverage & Debug Tooling
Test targets, gcov/lcov, sanitizers
13
Make as Automation & DevOps Tool
Task runner, Docker, install, packaging
14
CI/CD Integration
Deterministic builds, GitHub Actions, cache
15
Advanced Make & Debugging
--debug, dynamic rules, evaluation traps
16
Ecosystem, Alternatives & Mastery
CMake, Ninja, Meson, Bazel, production
The -j Flag
make -j4 # run at most 4 jobs simultaneously
make -j # unlimited jobs (usually bad — can starve system)
make -j$(nproc) # set jobs = number of logical CPU cores (recommended)
Auto-detect CPU Count in Makefile
# Detect CPU count portably (Linux nproc, macOS sysctl)
NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
# Offer a convenience target:
.PHONY: fast
fast:
$(MAKE) -j$(NPROC) all
Sample Source Files
Self-contained examples: Parallel-build examples compile main.c, utils.c, and parser.c simultaneously. Each translation unit is independent, making them ideal for demonstrating -j speed-ups.
main.c
/* main.c — entry point */
#include <stdio.h>
#include "utils.h"
#include "parser.h"
int main(void) {
utils_greet("Parallel Make");
parse_line("target: dep1 dep2");
return 0;
}
utils.h
/* utils.h */
#ifndef UTILS_H
#define UTILS_H
void utils_greet(const char *name);
#endif
utils.c
/* utils.c */
#include <stdio.h>
#include "utils.h"
void utils_greet(const char *name) {
printf("Hello, %s!\n", name);
}
parser.h
/* parser.h */
#ifndef PARSER_H
#define PARSER_H
void parse_line(const char *line);
#endif
parser.c
/* parser.c — minimal rule-line parser stub */
#include <stdio.h>
#include "parser.h"
void parse_line(const char *line) {
printf("Parsing: %s\n", line);
}