N/ NIFT LABS

Build systems / same-node experiment

The build
is a graph.

GNU Make, Ninja and Nift execute the same native project. What costs time when nothing changes—and when everything does?

GNU Make 4.4.1 / Ninja 1.13.2 / Nift v4.12.0 final candidate · Frozen identities

Four corpus sizes · two compile profiles · workers 1 / 2 / 4

COMMON BUILD GRAPHORACLE / PASS
input / schemaconfig.h+ private / sharedgenerated.cppN objectsgenerated.o10 archivesexecutable
same sources / compiler / flags / outputs
main.o also links directly (branch omitted)
three graph encodings · one executed-action oracle
GNU MakeExplicit, nonrecursive rules
Compiler-generated dependencies
NinjaOrdinary build edges
Compiler dependency database
NiftTracked native build actions
Ordering + explicit file dependencies

01 / noop

Nothing changed. Everything checked.

No-op includes graph loading and dependency checks. A zero action count is verified for every sample. These are warm prepared fixtures, not cold-machine boots.

Warm no-op scaling — GNU Make, Ninja and Nift
Warm no-op scaling. Both axes are logarithmic. SVG / PNG
Warm no-op scaling; light, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
1005.5054.51316.274100
1,00039.54518.15291.187100
5,000249.79281.941398.972100
10,000599.464171.847793.865100

02 / leaf

One source edit. The whole graph in view.

A byte change in one leaf source recompiles one object, updates its static archive and relinks the executable. Setup is excluded; graph traversal, these three actions and bookkeeping are included.

One-file incremental scaling — GNU Make, Ninja and Nift
One-file incremental scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
One-file incremental scaling; light, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
10077.73381.50395.12820
1,000204.766193.423269.97720
5,000912.416800.8301,113.41220
10,0002,257.8191,513.2472,207.78020

03 / fanout

A header edit expands the dirty set.

The private header reaches one object; a module header reaches 10% of translation units; the global header reaches all native leaf objects. Generated-input changes run both generator actions and rebuild their complete consumers.

Global-header rebuild scaling — GNU Make, Ninja and Nift
Global-header rebuild scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
Global-header rebuild scaling; light, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
100648.461703.048700.57310
1,0005,432.9646,223.0956,155.3335
5,00027,891.97030,885.67132,085.1263
10,00059,402.05565,274.19469,193.4983
Complete native workload matrix; 1,000 light translation units, j4; median ms
WorkloadGNU MakeNinjaNiftActionsSamples / cell
Clean full5,533.9216,271.2956,256.29010155
Warm no-op39.54518.15291.1870100
Leaf source204.766193.423269.977320
Private header204.195199.589273.849320
Module header712.879764.652862.0831025
Global header5,432.9646,223.0956,155.33310115
Generated input5,471.6296,432.4476,093.18510145
10 sources296.715311.228379.102215
10% sources805.167797.504919.9811115
Targeted module83.60761.837113.244220
Relink only162.216143.656209.825120
Clean + rebuild5,558.1086,031.8886,070.62410155

04 / full

When the compiler owns the clock.

Clean full removes outputs before timing. Clean + rebuild includes a common removal/recreation command inside timing. Native graph size is translation units + 15 actions: generators, two additional objects, ten archives and a link.

Clean full build scaling — GNU Make, Ninja and Nift
Clean full build scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
Clean full build scaling; light, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
100729.312789.025784.29410
1,0005,533.9216,271.2956,256.2905
5,00028,646.41730,593.97531,739.1263
10,00058,543.31461,651.37068,583.8983

Large full-build cells have three measured samples. Their min–max spreads remain visible in the distributions below; no high-percentile or universal ranking claim follows from them.

05 / tiny

Just the orchestrator and tiny actions.

A separate graph contains independent deterministic FNV hash actions. It exposes loading, checking and dispatch costs; it is not C++ throughput and does not cover every DAG shape.

Tiny-action no-op scaling — GNU Make, Ninja and Nift
Tiny-action no-op scaling. Both axes are logarithmic. SVG / PNG
Tiny-action no-op scaling; independent actions, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
1002.6493.69611.853100
1,00010.28713.04368.947100
10,00096.950108.268617.328100
Fully dirty tiny-action scaling — GNU Make, Ninja and Nift
Fully dirty tiny-action scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
Fully dirty tiny-action scaling; independent actions, j4; median ms
CorpusGNU MakeNinjaNiftSamples / cell
10061.88189.075124.9615
1,000546.006802.6151,046.2925
10,0006,065.2818,820.17215,846.3105

06 / parallel

Same graph. Different worker limits.

Every command uses the same allowed four logical CPUs. Worker limits are 1, 2 or 4. The heavier profile instantiates sixteen independent depth-128 templates per translation unit; its final binary behavior is unchanged. Compare workers within a fixed size/profile.

Clean full build for 1,000 heavy translation units at one, two and four workers
Same compiler-heavy graph, same CPU set. SVG / PNG
Same chart cells: 1,000 heavy translation units; median ms
Corpus / profile / workersGNU MakeNinjaNiftSamples / cell
1,000 / heavy / j157,589.25058,931.57658,537.5903
1,000 / heavy / j229,326.79330,084.08230,222.0565
1,000 / heavy / j414,866.71515,002.57815,621.5805
100-unit light and heavy worker comparisons
Additional fixed-size worker comparisons; median ms
Corpus / profile / workersGNU MakeNinjaNiftSamples / cell
100 / light / j12,268.1542,413.3802,426.64410
100 / light / j21,240.8971,303.0061,372.08910
100 / light / j4729.312789.025784.29410
100 / heavy / j15,985.9386,143.8116,268.82510
100 / heavy / j22,972.9693,088.9683,155.31910
100 / heavy / j41,520.3461,575.2481,621.69210

07 / correctness

Fast only counts when the work is right.

All 9,348 retained official observations, including warmups, pass the expected action-set and output-hash checks. A common wrapper records each generator, compiler, archive and link invocation. Missing, extra or duplicate work aborts the campaign.

Same bytes, same behavior

Generated files, objects, deterministic archives and executable hashes agree across all participants for each case. The executable verifies a known sum. Tiny outputs have an independent Python oracle.

Discovered dependencies checked

Make uses -MMD -MP depfiles; Ninja imports them into its dependency database. Every discovered object include closure is audited against Nift’s explicit file inputs.

Nift’s “depends” controls order, not invalidation. File dependencies include prerequisite outputs. Named Nift targets are forced; the targeted comparison starts with a dirty module for all three systems. An untimed subsequent full build verifies the final executable.

Deleted / renamed input and failure boundaries

Local certification checks unrelated files, source renames and deletions. Rename setup updates each graph and removes the affected object and obsolete depfile before timing. Make/Ninja reject a deleted source before dispatch; Nift attempts that compile and blocks downstream archive/link actions. Nonzero status and no unrelated actions are required. These failure diagnostics are not performance samples.

08 / resources

Memory follows the same workload.

CPU time and maximum-child RSS are retained for every timed command. Linux wait4 RSS is a child high-water measure, not summed simultaneous memory across parallel compiler processes. Wall time remains the primary metric.

Warm no-op memory scaling — GNU Make, Ninja and Nift
Warm no-op memory scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
Warm no-op memory scaling; light, j4; median MiB
CorpusGNU MakeNinjaNiftSamples / cell
1002.2504.5005.875100
1,0004.0006.0436.750100
5,00011.62513.12912.324100
10,00021.20721.80919.148100
One-file incremental memory scaling — GNU Make, Ninja and Nift
One-file incremental memory scaling. Task-count spacing is logarithmic; the value axis starts at zero. SVG / PNG
One-file incremental memory scaling; light, j4; median MiB
CorpusGNU MakeNinjaNiftSamples / cell
10021.25021.25021.31220
1,00055.21555.21555.21520
5,000239.746239.625239.68620
10,000469.867469.867469.86320

7 of 60 cells with fewer than ten samples have min–max ranges exceeding 25% of their medians. This is a descriptive variability flag, not a confidence interval. Retained distributions below identify every cell; treat these medians cautiously.

Every measured cell: timing distributions, CPU and memory
All cells; p95 only n≥20, p99 only n≥100; no outlier deletion
CaseSystemnMedian msMin–max msp95 msp99 msCPU msRSS MiB
native / 100 / heavy / j1 / Clean fullGNU Make105,985.9385,722.713–6,320.794——5,992.37426.875
native / 100 / heavy / j1 / Clean fullNift106,268.8255,959.749–6,564.507——6,278.98226.875
native / 100 / heavy / j1 / Clean fullNinja106,143.8115,868.723–6,853.849——6,143.49026.875
native / 100 / heavy / j1 / Global headerGNU Make105,783.6285,591.709–6,530.987——5,790.24726.750
native / 100 / heavy / j1 / Global headerNift106,043.6885,825.365–6,482.011——6,050.82426.750
native / 100 / heavy / j1 / Global headerNinja105,964.9545,770.991–6,556.886——5,978.88226.750
native / 100 / heavy / j1 / Leaf sourceGNU Make20114.982107.283–136.947135.502—114.98326.625
native / 100 / heavy / j1 / Leaf sourceNift20152.994136.785–166.191166.058—151.00026.625
native / 100 / heavy / j1 / Leaf sourceNinja20115.900107.950–139.359135.489—116.08826.625
native / 100 / heavy / j1 / Warm no-opGNU Make1005.6424.688–14.2657.2448.4825.4432.250
native / 100 / heavy / j1 / Warm no-opNift10031.75829.138–43.14638.50241.31829.4445.875
native / 100 / heavy / j1 / Warm no-opNinja1004.6893.844–7.6745.9936.6694.4644.500
native / 100 / heavy / j2 / Clean fullGNU Make102,972.9692,912.111–3,093.372——5,901.63126.875
native / 100 / heavy / j2 / Clean fullNift103,155.3193,079.732–3,343.947——6,233.34226.875
native / 100 / heavy / j2 / Clean fullNinja103,088.9682,987.927–3,369.015——6,124.95726.875
native / 100 / heavy / j2 / Global headerGNU Make102,975.3452,886.278–3,329.712——5,904.83126.750
native / 100 / heavy / j2 / Global headerNift103,053.9712,962.060–3,467.672——6,043.49926.750
native / 100 / heavy / j2 / Global headerNinja103,086.1202,940.217–3,171.181——6,124.71926.750
native / 100 / heavy / j2 / Leaf sourceGNU Make20119.926109.856–157.683134.869—120.81326.625
native / 100 / heavy / j2 / Leaf sourceNift20136.735127.354–169.041159.793—148.28126.750
native / 100 / heavy / j2 / Leaf sourceNinja20116.672106.044–143.431128.152—116.93126.625
native / 100 / heavy / j2 / Warm no-opGNU Make1005.9074.855–10.7587.6828.4535.6352.250
native / 100 / heavy / j2 / Warm no-opNift10022.66719.009–34.60526.28830.99732.7075.875
native / 100 / heavy / j2 / Warm no-opNinja1004.7083.937–11.5596.0937.0674.4634.500
native / 100 / heavy / j4 / Clean fullGNU Make101,520.3461,421.245–1,710.032——5,728.54226.875
native / 100 / heavy / j4 / Clean fullNift101,621.6921,529.599–1,879.393——6,104.62526.875
native / 100 / heavy / j4 / Clean fullNinja101,575.2481,514.319–1,917.993——5,908.90426.875
native / 100 / heavy / j4 / Global headerGNU Make101,569.2041,530.647–1,827.384——5,995.60926.750
native / 100 / heavy / j4 / Global headerNift101,583.1931,512.789–1,851.546——6,046.58526.750
native / 100 / heavy / j4 / Global headerNinja101,552.6181,498.147–1,720.719——5,912.91026.750
native / 100 / heavy / j4 / Leaf sourceGNU Make20113.777107.381–176.067151.589—114.79226.625
native / 100 / heavy / j4 / Leaf sourceNift20131.257122.750–220.118175.228—149.15326.625
native / 100 / heavy / j4 / Leaf sourceNinja20116.688107.503–131.220128.990—116.86826.625
native / 100 / heavy / j4 / Warm no-opGNU Make1005.7984.713–10.2787.5238.1845.5852.250
native / 100 / heavy / j4 / Warm no-opNift10016.98814.720–28.04320.53925.42235.7695.875
native / 100 / heavy / j4 / Warm no-opNinja1004.6063.856–6.1845.9076.0524.3994.500
native / 100 / light / j1 / 10 sourcesGNU Make20316.716286.095–345.281336.226—316.39821.375
native / 100 / light / j1 / 10 sourcesNift20368.838345.648–597.891425.455—368.32021.375
native / 100 / light / j1 / 10 sourcesNinja20330.610309.778–452.919417.092—329.54521.375
native / 100 / light / j1 / 10% sourcesGNU Make20334.803291.595–414.637389.821—334.31621.375
native / 100 / light / j1 / 10% sourcesNift20409.088336.118–835.086459.243—407.73721.375
native / 100 / light / j1 / 10% sourcesNinja20360.048311.554–461.407406.700—361.78221.375
native / 100 / light / j1 / Clean + rebuildGNU Make102,267.9142,176.474–2,486.282——2,274.66326.875
native / 100 / light / j1 / Clean + rebuildNift102,528.3162,403.278–3,333.791——2,536.60126.875
native / 100 / light / j1 / Clean + rebuildNinja102,450.7412,280.383–2,724.854——2,462.09926.875
native / 100 / light / j1 / Clean fullGNU Make102,268.1542,234.737–2,555.248——2,274.22426.875
native / 100 / light / j1 / Clean fullNift102,426.6442,363.306–2,842.748——2,434.13526.875
native / 100 / light / j1 / Clean fullNinja102,413.3802,276.700–3,065.601——2,425.07926.875
native / 100 / light / j1 / Generated inputGNU Make102,240.1862,151.073–2,569.968——2,246.89521.375
native / 100 / light / j1 / Generated inputNift102,482.6622,344.159–2,926.767——2,490.28821.375
native / 100 / light / j1 / Generated inputNinja102,355.5122,239.327–2,551.811——2,367.51721.375
native / 100 / light / j1 / Global headerGNU Make102,123.8472,006.392–2,950.547——2,130.14021.375
native / 100 / light / j1 / Global headerNift102,337.7092,251.378–2,642.376——2,340.26521.375
native / 100 / light / j1 / Global headerNinja102,254.9532,112.348–2,544.600——2,266.00321.375
native / 100 / light / j1 / Leaf sourceGNU Make2085.25977.104–112.53795.506—85.27221.375
native / 100 / light / j1 / Leaf sourceNift20117.943109.969–147.064126.433—115.99821.250
native / 100 / light / j1 / Leaf sourceNinja2087.38877.807–137.363106.631—87.31421.250
native / 100 / light / j1 / Module headerGNU Make20258.499243.737–287.124286.657—259.18221.375
native / 100 / light / j1 / Module headerNift20306.041287.673–358.795352.164—305.31321.375
native / 100 / light / j1 / Module headerNinja20270.954258.552–324.009307.123—271.99321.375
native / 100 / light / j1 / Warm no-opGNU Make1005.2834.601–12.8176.7547.4885.1032.250
native / 100 / light / j1 / Warm no-opNift10030.82828.267–90.55238.47852.62828.7355.875
native / 100 / light / j1 / Warm no-opNinja1004.3353.772–9.3795.3626.8004.1814.500
native / 100 / light / j1 / Private headerGNU Make2077.18272.127–83.14281.160—77.19721.250
native / 100 / light / j1 / Private headerNift20109.545101.005–125.242124.597—107.84421.250
native / 100 / light / j1 / Private headerNinja2079.56674.901–95.14384.612—79.58121.250
native / 100 / light / j1 / Relink onlyGNU Make2055.96350.713–70.12268.724—55.85314.730
native / 100 / light / j1 / Relink onlyNift2083.56473.093–113.11096.376—81.45314.625
native / 100 / light / j1 / Relink onlyNinja2058.60547.235–77.98272.179—58.54514.625
native / 100 / light / j1 / Targeted moduleGNU Make2035.19429.866–38.91638.321—35.25021.250
native / 100 / light / j1 / Targeted moduleNift2044.79140.297–50.75750.292—42.92221.250
native / 100 / light / j1 / Targeted moduleNinja2036.25931.011–41.02941.005—36.05021.250
native / 100 / light / j2 / 10 sourcesGNU Make20196.533182.965–319.182216.203—335.98821.375
native / 100 / light / j2 / 10 sourcesNift20240.619215.170–310.523292.120—411.65521.375
native / 100 / light / j2 / 10 sourcesNinja20213.477194.490–273.749237.876—367.26721.375
native / 100 / light / j2 / 10% sourcesGNU Make20199.372179.801–220.661220.580—340.46821.375
native / 100 / light / j2 / 10% sourcesNift20244.922214.847–281.223268.843—416.10721.375
native / 100 / light / j2 / 10% sourcesNinja20211.447189.921–241.485233.686—365.27621.375
native / 100 / light / j2 / Clean + rebuildGNU Make101,266.4261,200.621–1,645.973——2,476.47926.875
native / 100 / light / j2 / Clean + rebuildNift101,401.8551,327.270–1,528.235——2,723.95826.938
native / 100 / light / j2 / Clean + rebuildNinja101,299.5591,272.270–1,427.544——2,537.62826.875
native / 100 / light / j2 / Clean fullGNU Make101,240.8971,199.727–1,351.541——2,423.49426.875
native / 100 / light / j2 / Clean fullNift101,372.0891,298.872–1,999.948——2,676.55826.875
native / 100 / light / j2 / Clean fullNinja101,303.0061,231.906–1,432.804——2,554.58826.875
native / 100 / light / j2 / Generated inputGNU Make101,171.9411,129.173–1,302.050——2,256.26121.375
native / 100 / light / j2 / Generated inputNift101,317.6601,242.511–1,430.758——2,534.26421.375
native / 100 / light / j2 / Generated inputNinja101,271.1021,202.356–1,407.258——2,461.67121.375
native / 100 / light / j2 / Global headerGNU Make101,211.2181,079.548–1,434.119——2,364.04621.375
native / 100 / light / j2 / Global headerNift101,296.4271,189.483–1,472.255——2,528.51421.375
native / 100 / light / j2 / Global headerNinja101,186.3831,152.084–1,330.347——2,318.82821.375
native / 100 / light / j2 / Leaf sourceGNU Make2080.03074.077–101.71591.796—81.12721.250
native / 100 / light / j2 / Leaf sourceNift20105.12095.967–120.057112.734—115.69221.250
native / 100 / light / j2 / Leaf sourceNinja2084.76275.191–92.25191.226—84.94521.250
native / 100 / light / j2 / Module headerGNU Make20178.115161.322–300.742205.244—294.67721.375
native / 100 / light / j2 / Module headerNift20205.253188.881–290.997257.932—338.53121.375
native / 100 / light / j2 / Module headerNinja20179.419161.888–205.599189.876—297.56621.375
native / 100 / light / j2 / Warm no-opGNU Make1005.7574.814–8.0747.1317.4425.5712.250
native / 100 / light / j2 / Warm no-opNift10022.60418.726–35.35826.86433.36432.3265.875
native / 100 / light / j2 / Warm no-opNinja1004.6693.790–7.6986.1426.7834.5124.500
native / 100 / light / j2 / Private headerGNU Make2081.38875.568–93.52292.225—82.16521.375
native / 100 / light / j2 / Private headerNift20103.15394.688–119.348119.271—114.55921.250
native / 100 / light / j2 / Private headerNinja2084.35076.866–103.99198.122—84.49321.250
native / 100 / light / j2 / Relink onlyGNU Make2052.00548.435–81.48456.194—51.96614.625
native / 100 / light / j2 / Relink onlyNift2069.13965.290–91.36577.939—78.19214.625
native / 100 / light / j2 / Relink onlyNinja2053.59147.762–60.29659.527—53.60614.625
native / 100 / light / j2 / Targeted moduleGNU Make2031.12329.606–41.08036.148—31.19521.250
native / 100 / light / j2 / Targeted moduleNift2042.00339.477–49.37545.609—39.98421.250
native / 100 / light / j2 / Targeted moduleNinja2033.29230.253–47.01938.919—33.31821.375
native / 100 / light / j4 / 10 sourcesGNU Make20129.710120.751–144.565142.082—331.70721.375
native / 100 / light / j4 / 10 sourcesNift20155.761144.585–175.773171.547—410.75621.375
native / 100 / light / j4 / 10 sourcesNinja20142.997129.201–165.759161.347—366.93521.375
native / 100 / light / j4 / 10% sourcesGNU Make20132.099121.333–144.900144.165—343.29721.375
native / 100 / light / j4 / 10% sourcesNift20159.410140.501–176.815174.504—415.99021.375
native / 100 / light / j4 / 10% sourcesNinja20140.155127.388–164.472156.082—361.91921.375
native / 100 / light / j4 / Clean + rebuildGNU Make10711.932655.839–747.508——2,507.45826.875
native / 100 / light / j4 / Clean + rebuildNift10778.479734.072–858.297——2,745.38626.875
native / 100 / light / j4 / Clean + rebuildNinja10753.182689.039–787.935——2,609.63726.812
native / 100 / light / j4 / Clean fullGNU Make10729.312660.877–780.622——2,575.48526.875
native / 100 / light / j4 / Clean fullNift10784.294740.746–1,028.689——2,773.45426.875
native / 100 / light / j4 / Clean fullNinja10789.025740.083–833.537——2,727.39626.875
native / 100 / light / j4 / Generated inputGNU Make10692.023665.559–922.293——2,437.43921.375
native / 100 / light / j4 / Generated inputNift10788.988737.486–842.355——2,745.39921.375
native / 100 / light / j4 / Generated inputNinja10754.916711.082–816.778——2,592.47021.375
native / 100 / light / j4 / Global headerGNU Make10648.461595.151–685.640——2,326.02121.375
native / 100 / light / j4 / Global headerNift10700.573658.780–817.407——2,536.89521.375
native / 100 / light / j4 / Global headerNinja10703.048645.669–755.829——2,483.60921.375
native / 100 / light / j4 / Leaf sourceGNU Make2077.73373.935–94.27088.831—78.54121.250
native / 100 / light / j4 / Leaf sourceNift2095.12889.212–200.524166.828—113.28621.312
native / 100 / light / j4 / Leaf sourceNinja2081.50374.295–203.091103.018—82.00821.250
native / 100 / light / j4 / Module headerGNU Make20130.604113.681–163.984153.183—288.12321.375
native / 100 / light / j4 / Module headerNift20158.353131.425–177.932171.770—346.65921.375
native / 100 / light / j4 / Module headerNinja20141.778124.300–156.338154.258—309.70221.375
native / 100 / light / j4 / Warm no-opGNU Make1005.5054.746–8.8737.0207.3885.3482.250
native / 100 / light / j4 / Warm no-opNift10016.27414.077–28.28219.41520.86235.2145.875
native / 100 / light / j4 / Warm no-opNinja1004.5133.894–6.3105.7726.0624.3454.500
native / 100 / light / j4 / Private headerGNU Make2086.74974.331–96.66495.269—87.50221.250
native / 100 / light / j4 / Private headerNift20102.49990.299–119.419118.193—123.29321.250
native / 100 / light / j4 / Private headerNinja2085.06775.803–108.32298.022—85.29421.250
native / 100 / light / j4 / Relink onlyGNU Make2056.38050.152–64.24762.604—56.15614.730
native / 100 / light / j4 / Relink onlyNift2067.69763.596–89.44580.498—88.28114.730
native / 100 / light / j4 / Relink onlyNinja2055.74850.034–67.64865.021—55.71814.730
native / 100 / light / j4 / Targeted moduleGNU Make2035.95229.967–75.17352.695—35.96921.250
native / 100 / light / j4 / Targeted moduleNift2045.99941.264–76.58455.275—44.85021.312
native / 100 / light / j4 / Targeted moduleNinja2035.84532.396–57.51843.692—35.82221.250
native / 1,000 / heavy / j1 / Clean fullGNU Make357,589.25056,709.854–57,656.857——57,672.31255.211
native / 1,000 / heavy / j1 / Clean fullNift358,537.59058,100.327–59,695.428——58,622.69255.340
native / 1,000 / heavy / j1 / Clean fullNinja358,931.57658,245.080–59,517.981——59,119.16555.211
native / 1,000 / heavy / j1 / Leaf sourceGNU Make20278.283243.652–339.340306.788—277.92955.215
native / 1,000 / heavy / j1 / Leaf sourceNift20486.091433.082–591.477568.912—480.51155.336
native / 1,000 / heavy / j1 / Leaf sourceNinja20248.661223.316–332.739299.038—247.90955.275
native / 1,000 / heavy / j1 / Warm no-opGNU Make10040.07735.297–88.33050.77073.12539.6684.000
native / 1,000 / heavy / j1 / Warm no-opNift100230.478210.578–337.500278.438327.444225.6366.625
native / 1,000 / heavy / j1 / Warm no-opNinja10018.46316.390–31.32223.45726.63818.2626.043
native / 1,000 / heavy / j2 / Clean fullGNU Make529,326.79329,030.804–29,956.365——58,509.66155.215
native / 1,000 / heavy / j2 / Clean fullNift530,222.05629,781.829–30,498.959——60,236.69655.215
native / 1,000 / heavy / j2 / Clean fullNinja530,084.08229,926.578–30,662.036——59,997.10255.215
native / 1,000 / heavy / j2 / Leaf sourceGNU Make20275.635246.234–303.921295.155—283.38055.211
native / 1,000 / heavy / j2 / Leaf sourceNift20379.869335.552–517.907439.322—473.33855.215
native / 1,000 / heavy / j2 / Leaf sourceNinja20248.945224.883–279.112278.352—248.78155.336
native / 1,000 / heavy / j2 / Warm no-opGNU Make10039.28834.432–66.67353.09758.45638.9994.000
native / 1,000 / heavy / j2 / Warm no-opNift100136.578121.963–172.059166.119170.224233.2746.625
native / 1,000 / heavy / j2 / Warm no-opNinja10018.34416.481–24.86723.63324.83218.1366.043
native / 1,000 / heavy / j4 / Clean fullGNU Make514,866.71514,680.185–16,151.254——57,789.54255.215
native / 1,000 / heavy / j4 / Clean fullNift515,621.58014,956.047–15,893.912——60,845.33355.340
native / 1,000 / heavy / j4 / Clean fullNinja515,002.57814,640.019–15,782.583——58,333.58455.215
native / 1,000 / heavy / j4 / Leaf sourceGNU Make20249.348232.356–306.710288.413—259.17655.336
native / 1,000 / heavy / j4 / Leaf sourceNift20325.288291.427–442.971425.183—500.80755.336
native / 1,000 / heavy / j4 / Leaf sourceNinja20234.936212.413–278.607263.533—234.52055.336
native / 1,000 / heavy / j4 / Warm no-opGNU Make10039.22534.251–58.99150.14952.81338.9474.000
native / 1,000 / heavy / j4 / Warm no-opNift10091.73079.879–139.865105.338120.365258.7986.750
native / 1,000 / heavy / j4 / Warm no-opNinja10018.59016.467–27.36723.51225.25018.3316.043
native / 1,000 / light / j4 / 10 sourcesGNU Make5296.715284.799–340.240——671.33555.215
native / 1,000 / light / j4 / 10 sourcesNift5379.102363.130–384.421——956.83655.281
native / 1,000 / light / j4 / 10 sourcesNinja5311.228281.390–313.248——704.35155.215
native / 1,000 / light / j4 / 10% sourcesGNU Make5805.167742.668–858.285——2,501.87955.336
native / 1,000 / light / j4 / 10% sourcesNift5919.981877.521–1,118.752——3,100.90455.340
native / 1,000 / light / j4 / 10% sourcesNinja5797.504784.047–835.957——2,559.29555.336
native / 1,000 / light / j4 / Clean + rebuildGNU Make55,558.1085,423.651–5,916.279——21,091.91155.336
native / 1,000 / light / j4 / Clean + rebuildNift56,070.6245,997.143–6,508.882——23,051.54155.281
native / 1,000 / light / j4 / Clean + rebuildNinja56,031.8885,971.937–6,279.580——22,321.87555.336
native / 1,000 / light / j4 / Clean fullGNU Make55,533.9215,512.765–5,710.619——21,047.41955.336
native / 1,000 / light / j4 / Clean fullNift56,256.2905,880.488–6,315.222——23,816.10155.215
native / 1,000 / light / j4 / Clean fullNinja56,271.2956,266.101–6,319.893——23,188.03555.215
native / 1,000 / light / j4 / Generated inputGNU Make55,471.6295,413.335–5,561.644——20,792.00655.211
native / 1,000 / light / j4 / Generated inputNift56,093.1855,896.502–6,305.001——23,069.81655.215
native / 1,000 / light / j4 / Generated inputNinja56,432.4475,877.086–6,571.671——23,559.48055.285
native / 1,000 / light / j4 / Global headerGNU Make55,432.9645,297.424–5,748.672——20,713.67255.215
native / 1,000 / light / j4 / Global headerNift56,155.3335,848.488–6,719.958——23,074.11555.215
native / 1,000 / light / j4 / Global headerNinja56,223.0955,804.096–6,543.351——23,042.02755.336
native / 1,000 / light / j4 / Leaf sourceGNU Make20204.766190.711–242.157232.647—212.22555.215
native / 1,000 / light / j4 / Leaf sourceNift20269.977256.411–308.050285.682—435.78155.215
native / 1,000 / light / j4 / Leaf sourceNinja20193.423178.709–277.057276.347—192.84555.215
native / 1,000 / light / j4 / Module headerGNU Make5712.879676.091–1,071.697——2,250.43055.336
native / 1,000 / light / j4 / Module headerNift5862.083790.391–919.339——2,760.24055.336
native / 1,000 / light / j4 / Module headerNinja5764.652747.346–780.768——2,407.86855.215
native / 1,000 / light / j4 / Warm no-opGNU Make10039.54535.601–59.12054.07757.30839.2914.000
native / 1,000 / light / j4 / Warm no-opNift10091.18780.824–148.468104.934145.377258.5536.750
native / 1,000 / light / j4 / Warm no-opNinja10018.15216.151–32.83222.97125.13717.9246.043
native / 1,000 / light / j4 / Private headerGNU Make20204.195189.975–259.380222.276—212.15655.215
native / 1,000 / light / j4 / Private headerNift20273.849254.675–330.469306.743—443.03755.215
native / 1,000 / light / j4 / Private headerNinja20199.589182.381–332.062293.654—198.99355.311
native / 1,000 / light / j4 / Relink onlyGNU Make20162.216148.202–191.834186.986—162.12655.215
native / 1,000 / light / j4 / Relink onlyNift20209.825201.399–341.896268.725—375.87755.215
native / 1,000 / light / j4 / Relink onlyNinja20143.656132.779–190.886188.634—143.64455.311
native / 1,000 / light / j4 / Targeted moduleGNU Make2083.60776.604–111.96395.570—84.02021.250
native / 1,000 / light / j4 / Targeted moduleNift20113.244101.244–193.307135.451—109.84621.250
native / 1,000 / light / j4 / Targeted moduleNinja2061.83756.861–72.22271.461—61.34021.250
native / 5,000 / light / j4 / Clean fullGNU Make328,646.41728,430.429–29,715.624——106,392.787239.625
native / 5,000 / light / j4 / Clean fullNift331,739.12631,560.016–32,247.618——119,755.258239.746
native / 5,000 / light / j4 / Clean fullNinja330,593.97530,553.869–31,521.431——113,329.547239.746
native / 5,000 / light / j4 / Global headerGNU Make327,891.97027,632.435–28,780.116——105,431.434239.621
native / 5,000 / light / j4 / Global headerNift332,085.12630,756.628–32,950.003——118,540.479239.750
native / 5,000 / light / j4 / Global headerNinja330,885.67130,249.432–32,113.496——113,899.318239.625
native / 5,000 / light / j4 / Leaf sourceGNU Make20912.416851.991–1,640.6701,417.465—930.288239.746
native / 5,000 / light / j4 / Leaf sourceNift201,113.4121,024.752–1,359.0741,310.663—1,928.602239.686
native / 5,000 / light / j4 / Leaf sourceNinja20800.830711.097–1,174.7661,011.402—796.675239.625
native / 5,000 / light / j4 / Warm no-opGNU Make100249.792227.103–382.754297.965361.657249.43311.625
native / 5,000 / light / j4 / Warm no-opNift100398.972368.024–670.579450.516537.1151,189.58812.324
native / 5,000 / light / j4 / Warm no-opNinja10081.94173.507–133.346101.187133.22681.69613.129
native / 10,000 / light / j4 / Clean fullGNU Make358,543.31454,639.682–58,786.718——215,224.555469.855
native / 10,000 / light / j4 / Clean fullNift368,583.89864,970.624–72,955.162——256,064.935469.859
native / 10,000 / light / j4 / Clean fullNinja361,651.37061,110.581–62,756.543——226,711.675469.859
native / 10,000 / light / j4 / Global headerGNU Make359,402.05558,314.192–62,021.263——220,784.206469.867
native / 10,000 / light / j4 / Global headerNift369,193.49865,510.894–72,103.220——256,153.674469.867
native / 10,000 / light / j4 / Global headerNinja365,274.19462,362.250–65,866.624——232,885.084469.988
native / 10,000 / light / j4 / Leaf sourceGNU Make202,257.8191,851.011–2,885.8292,664.718—2,269.498469.867
native / 10,000 / light / j4 / Leaf sourceNift202,207.7802,065.926–3,146.5712,863.298—3,806.693469.863
native / 10,000 / light / j4 / Leaf sourceNinja201,513.2471,417.527–1,976.7021,925.440—1,504.206469.867
native / 10,000 / light / j4 / Warm no-opGNU Make100599.464549.001–963.619730.815802.358598.54921.207
native / 10,000 / light / j4 / Warm no-opNift100793.865734.689–1,187.182923.9691,095.2472,397.13719.148
native / 10,000 / light / j4 / Warm no-opNinja100171.847148.470–263.559216.665254.270171.61121.809
tiny / 100 / light / j1 / 10 sourcesGNU Make2019.44416.651–34.40633.293—19.5632.125
tiny / 100 / light / j1 / 10 sourcesNift2050.22045.088–101.31065.153—48.9907.375
tiny / 100 / light / j1 / 10 sourcesNinja2030.45925.575–124.18560.532—30.9554.375
tiny / 100 / light / j1 / Clean fullGNU Make5154.068150.503–169.828——156.3802.250
tiny / 100 / light / j1 / Clean fullNift5300.467277.849–323.654——303.4037.375
tiny / 100 / light / j1 / Clean fullNinja5246.569235.172–269.460——252.2234.375
tiny / 100 / light / j1 / Leaf sourceGNU Make504.1973.539–5.6545.266—4.0832.125
tiny / 100 / light / j1 / Leaf sourceNift5026.05722.060–32.17831.059—24.3747.375
tiny / 100 / light / j1 / Leaf sourceNinja506.9395.899–9.9568.259—6.8444.375
tiny / 100 / light / j1 / Warm no-opGNU Make1002.4672.045–4.4653.4103.7922.2972.125
tiny / 100 / light / j1 / Warm no-opNift10021.49419.243–37.97927.06235.07419.9145.750
tiny / 100 / light / j1 / Warm no-opNinja1003.6313.083–5.6474.5905.5313.4434.375
tiny / 100 / light / j2 / 10 sourcesGNU Make2012.6709.834–15.48715.233—22.0542.125
tiny / 100 / light / j2 / 10 sourcesNift2035.39130.406–41.37841.123—58.2977.250
tiny / 100 / light / j2 / 10 sourcesNinja2019.99015.483–26.10525.695—33.9574.375
tiny / 100 / light / j2 / Clean fullGNU Make582.28776.685–90.180——165.5962.250
tiny / 100 / light / j2 / Clean fullNift5156.243151.656–183.317——305.4987.375
tiny / 100 / light / j2 / Clean fullNinja5134.858129.131–148.210——263.2844.375
tiny / 100 / light / j2 / Leaf sourceGNU Make503.9173.278–5.3064.776—4.3222.125
tiny / 100 / light / j2 / Leaf sourceNift5019.11216.461–24.63421.743—25.3587.375
tiny / 100 / light / j2 / Leaf sourceNinja506.8295.587–12.7048.306—6.7284.375
tiny / 100 / light / j2 / Warm no-opGNU Make1002.5702.169–14.7893.5319.5682.4212.125
tiny / 100 / light / j2 / Warm no-opNift10015.34713.006–28.43818.54623.44621.5745.750
tiny / 100 / light / j2 / Warm no-opNinja1003.6613.110–11.4804.9895.6503.4684.375
tiny / 100 / light / j4 / 10 sourcesGNU Make208.8307.722–10.95210.130—20.4612.125
tiny / 100 / light / j4 / 10 sourcesNift2025.14321.085–37.83428.554—60.1007.500
tiny / 100 / light / j4 / 10 sourcesNinja2013.43811.782–16.44516.091—30.9564.375
tiny / 100 / light / j4 / Clean fullGNU Make561.88153.696–71.408——177.9972.250
tiny / 100 / light / j4 / Clean fullNift5124.961104.881–137.497——396.9767.500
tiny / 100 / light / j4 / Clean fullNinja589.07584.547–108.412——282.5084.250
tiny / 100 / light / j4 / Leaf sourceGNU Make504.1643.325–5.7095.017—4.5142.125
tiny / 100 / light / j4 / Leaf sourceNift5016.08414.003–20.25719.241—27.8617.500
tiny / 100 / light / j4 / Leaf sourceNinja507.1425.629–10.2208.450—7.0634.375
tiny / 100 / light / j4 / Warm no-opGNU Make1002.6492.165–4.6693.5174.6262.4812.125
tiny / 100 / light / j4 / Warm no-opNift10011.85310.242–64.08015.53929.79123.8445.875
tiny / 100 / light / j4 / Warm no-opNinja1003.6963.013–6.0125.1555.7403.5164.375
tiny / 1,000 / light / j4 / 10 sourcesGNU Make2015.88414.510–21.63519.973—28.5312.750
tiny / 1,000 / light / j4 / 10 sourcesNift2082.43872.976–94.76594.124—225.3997.875
tiny / 1,000 / light / j4 / 10 sourcesNinja2023.77620.922–44.81729.731—42.3915.500
tiny / 1,000 / light / j4 / Clean fullGNU Make5546.006504.409–571.283——1,704.0964.125
tiny / 1,000 / light / j4 / Clean fullNift51,046.2921,036.813–1,205.611——3,490.3308.250
tiny / 1,000 / light / j4 / Clean fullNinja5802.615741.503–919.609——2,715.7845.625
tiny / 1,000 / light / j4 / Leaf sourceGNU Make5011.0259.232–16.42814.367—12.4162.750
tiny / 1,000 / light / j4 / Leaf sourceNift5075.39166.079–95.57183.491—199.7958.000
tiny / 1,000 / light / j4 / Leaf sourceNinja5016.09714.389–24.32320.176—15.9595.500
tiny / 1,000 / light / j4 / Warm no-opGNU Make10010.2878.955–17.14013.61515.79410.0222.750
tiny / 1,000 / light / j4 / Warm no-opNift10068.94761.885–102.32478.93283.277189.8226.250
tiny / 1,000 / light / j4 / Warm no-opNinja10013.04311.190–17.41616.82417.18412.7905.500
tiny / 10,000 / light / j4 / 10 sourcesGNU Make5107.00797.032–129.824——122.3829.457
tiny / 10,000 / light / j4 / 10 sourcesNift5701.099627.058–761.636——2,074.69614.160
tiny / 10,000 / light / j4 / 10 sourcesNinja5129.400114.632–171.427——162.10415.668
tiny / 10,000 / light / j4 / Clean fullGNU Make56,065.2815,390.975–6,411.157——17,922.65124.660
tiny / 10,000 / light / j4 / Clean fullNift515,846.31014,372.754–16,424.706——48,808.12617.031
tiny / 10,000 / light / j4 / Clean fullNinja58,820.1728,136.562–9,289.870——28,629.33917.047
tiny / 10,000 / light / j4 / Leaf sourceGNU Make5099.10787.602–133.750119.435—100.5809.457
tiny / 10,000 / light / j4 / Leaf sourceNift50633.471564.557–848.179749.784—1,883.35214.285
tiny / 10,000 / light / j4 / Leaf sourceNinja50116.26498.691–145.030138.439—116.09315.793
tiny / 10,000 / light / j4 / Warm no-opGNU Make10096.95083.297–145.266113.719122.43896.6329.457
tiny / 10,000 / light / j4 / Warm no-opNift100617.328560.508–814.558696.483786.2611,844.70212.535
tiny / 10,000 / light / j4 / Warm no-opNinja100108.26896.195–140.030132.856140.024108.01915.793

09 / assessment

Different costs. Different strengths.

At 10,000 light translation units, Ninja has the lowest observed no-op median and Ninja has the lowest one-file median. At 1,000 heavy translation units / j4, GNU Make has the lowest full-build median. These are descriptive cell comparisons, not significance tests or a universal ranking.

Nift’s higher-level tracked-action model is directly exercised here, including generation, ordering and targeted builds. Any dependency-checking or dispatch cost stays in the measurements. The syscall diagnostic provides a bounded lead for future core work; no optimization was made for this campaign.

10 / method

The boundary travels with the number.

One fresh Linode, four logical CPUs and 8 GB RAM. Participants rotate within every round; scenario order rotates across graphs. Sources and known build state are restored, contents mutated and timestamp preconditions checked before the C timer starts. No cache flushing or cold-machine claim. Stdout/stderr are captured; default CLI status-output costs remain inside timing, while terminal painting is excluded. Every warmup and sample is preserved.

Make and Ninja edges contain the actual native inputs. Nift additionally retains its required tracked-item content and explicit file sidecars. The first trial’s extra Make/Ninja recipe-marker edges were rejected, corrected and recertified; a second trial’s one-rule-per-action Ninja encoding was also rejected in favour of shared compile/action rules. Neither provisional series is pooled here.

The matrix is bounded: every native scenario at 100/1,000 light; larger light graphs for scaling; heavy 100/1,000 for compiler and worker comparisons; independent tiny actions at 100/1,000/10,000. It is not every combination of size, profile and workers.

Nift source
c14d06835bca515a5281e41da0e40219c2d60ef0 · rebuilt final candidate
Suite source
e95bfe88ac9e1eaf67cfeb8f67020fc3373ab81e
Toolchain
GNU Make 4.4.1 / 1.13.2 / Nift v4.12.0 / g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 / GNU ld (GNU Binutils for Ubuntu) 2.42
Canonical evidence
Raw observations, definitions, action oracles, hashes and lifecycle
Reproduction
Fairness contract · Exact sample plan · Frozen source reconstruction
Diagnostics
Bounded no-op syscall handover; tracing counts are diagnostic, not elapsed-time results. Nift core was not modified.

Sample policy: 100 no-op; 20 modest increments; 5–10 multi-second work; three expensive large builds. All samples remain, with linear percentile interpolation at (n−1)q. Shared-host variability and this synthetic graph limit generalization.

Actual build invocations: 1,000 light units / j4

Nift’s worker count is set in its generated project configuration. The common C supervisor times each command; restoration and correctness checks surround it.

GNU Make: make -j4
Ninja: /opt/refresh-tools/bin/ninja -j4
Nift: /opt/nift/nift build

11 / practitioner view · opinion / qualitative

Would I actually use Nift?

This is Codex’s subjective engineering assessment from implementing and reviewing the equivalent Make, Ninja and Nift benchmark projects. It is qualitative commentary, separate from the measurements and canonical evidence above. No independent agent-productivity comparison was performed.

A real programmable build system, with caveats.

The campaign exercised tracked artifact ownership, prerequisite DAGs, stale-work detection, native actions, failure propagation, generated outputs, targeted builds and concurrent work. Those contracts worked within the tested cases; they do not certify every project or platform.

The adapter is not the only authoring model.

Python emitted equivalent graphs for all three participants. Nift’s generated tracking metadata, sidecars, hooks and recipe JSON were that adapter’s representation, including duplicated arguments. They are not proof that Nift inherently needs that authoring ceremony. Project scripts such as build.f, task scripts such as tasks/test.f and tasks/package.f, and discovered per-artifact hooks such as content/about.build.f offer another surface. Tracked items can supply incremental ownership where useful; a standalone script does not automatically give every subprocess tracked scheduling or invalidation.

Explicit semantics can help agents.

Artifact ownership, output identity, prerequisite ordering, invalidation inputs and lifecycle phase express distinct facts. Machine-readable contracts can make edits easier to inspect and check. Nift already rejects unknown prerequisites, self edges, duplicate edges and cycles; it does not infer every input read by an arbitrary command. Preserve the explicit model, eliminate duplicated/no-information ceremony, and add concise helpers above it. Fewer lines are not the main objective.

What I would choose today.

I would choose Nift for bespoke procedural workflows when the required APIs fit, and prefer its .f policy to complex Make expansion/shell recipes or directly authored Ninja. A tiny conventional native build still suits Make. For larger conventional C/C++ today, compiler-discovered header integration, engine maturity and surrounding native tooling keep established generators and Ninja attractive. Manual include-closure maintenance is a substantial adoption gap for changing C/C++ projects; it is an integration gap, not evidence that the ordering/invalidation split is fundamentally wrong.

Qualitative preferences; Nift choices assume the required APIs and artifact contracts fit
SituationCodex preference
Tiny conventional native projectOften Make
Bespoke procedural build / automationNift
Nontrivial procedural logic in directly authored NinjaNift
Huge generated native graph todayNinja backend is the conservative choice
Agent-maintained bespoke mixed workflowNift scripting + explicit artifact contracts
Website + assets + native helpers + tasksNift where a shared project model saves coordination
Mature-Nift counterfactualNo inherent requirement for Ninja as executor

Under a mature-Nift counterfactual.

Assume near-Ninja no-op performance, compiler depfile ingestion, safer “consumes output” authoring, ensure-versus-force targeting, a CLI worker override, graph query/explain tooling, reusable .build.f patterns and multi-output actions. These are hypothetical improvements, not findings or features certified by this campaign. Comparable required semantics and reliability are also necessary.

Under those assumptions, I see no inherent architectural requirement to retain Ninja as the executor. I might still choose CMake/Meson-style tooling where toolchain handling, IDE integration, package/dependency facilities, installation/export conventions, compile databases or platform support save substantial work. That would primarily be an ecosystem/tooling preference, rather than an inherently better build-engine architecture.

A shared website, scripting, build and task runtime is compelling when it reduces duplicated policy and lifecycle coordination. Specialized tools remain preferable when replacing their integrations would create more work than the shared runtime saves.

Full reasoning, qualifications and the revised decision matrix: Nift build-system DX review and follow-up. This opinion does not alter campaign conclusions or canonical measurement evidence.