From 2f040cc859a77f5aee5e3d751c9ba1b53126ced9 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Tue, 11 Aug 2026 21:16:19 +0100 Subject: [PATCH] Build C++ with an explicit build type `pixi run build` passed no `-DCMAKE_BUILD_TYPE`, so every `CMakeCache.txt` reads empty -- which looks like an unoptimised robot and is not one. `CMAKE_CXX_FLAGS` is seeded from the environment's `CXXFLAGS`, and the conda compilers export one: mote-01's real compile lines already carried `-O3`. `kinematic_icp` sets `CMAKE_BUILD_TYPE Release` in its own CMakeLists, shadowing the cache, so it already had `-O3 -DNDEBUG` too. Release is still worth stating: the level was otherwise a property of whichever toolchain the solve picked, differed per platform (`-O2` on linux-64), and vanished outside pixi. Not RelWithDebInfo -- config flags land after `CXXFLAGS`, so its `-O2` would lower the Pi's `-O3`. Verified: - Fresh build: `CMAKE_BUILD_TYPE:STRING=Release` in every package cache, on both a clean tree and mote-01. - colcon test, 7 first-party packages: 967 tests, 0 failures, 8 skipped. - `-ffp-contract=off` still applies, counted in the shipped aarch64 objects: the relay's library holds 0 fused multiply-adds before and after, `icp_odom_gate` (same -O3, no flag) holds 11 both times. - On-robot CPU, replaying one mapping bag into localization_launch.py both sides: 2.207 -> 2.200 CPU-s per 120 s window, -0.3% against a +/-0.7% spread. No effect, as predicted. Co-authored-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QsepttjgzDu4pNodahUobB --- docs/tuning/2026-08-11-cmake-build-type.md | 47 ++++++++++++++++++++++ mkdocs.yml | 1 + pixi.toml | 5 ++- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/tuning/2026-08-11-cmake-build-type.md diff --git a/docs/tuning/2026-08-11-cmake-build-type.md b/docs/tuning/2026-08-11-cmake-build-type.md new file mode 100644 index 0000000..00447c5 --- /dev/null +++ b/docs/tuning/2026-08-11-cmake-build-type.md @@ -0,0 +1,47 @@ +# Where the C++ optimisation flags come from, 2026-08-11 + +**Verdict: `pixi run build` passes `-DCMAKE_BUILD_TYPE=Release`, and it changes +nothing measurable.** An empty `CMAKE_BUILD_TYPE` in `build/*/CMakeCache.txt` +looks like an unoptimised robot and is not one — which is the thing worth +recording here. + +Empty means CMake appends no `CMAKE_CXX_FLAGS_`; it does not mean `-O0`, +because `CMAKE_CXX_FLAGS` is seeded from the environment's `CXXFLAGS` and the +conda compiler packages export one — `-O2` on linux-64, **`-O3` on +linux-aarch64**. mote-01's real compile lines carried `-O3` before this change. +`kinematic_icp` was never in question either: its own CMakeLists does +`set(CMAKE_BUILD_TYPE Release)` (`ros/CMakeLists.txt:26`), shadowing the cache, +so its objects already had `-O3 -DNDEBUG`. + +So the flag adds `-DNDEBUG` on the robot and takes linux-64 from `-O2` to `-O3`. +Its point is that the level is now stated by the repo rather than inherited from +whichever toolchain the solve picked, and survives a build outside pixi. +**Not `RelWithDebInfo`**: config flags are appended *after* `CXXFLAGS`, so its +`-O2` would beat the Pi's `-O3` and lower optimisation. + +## Measured + +Replaying mapping bag `20260729_160009` into `localization_launch.py` +(`use_sim_time:=true`) drives the real ICP at the recorded 10.0 Hz, identical +input each run, so only the binary differs. Container `utime + stime` over a +120 s window, 3 reps a side, rebuild between: + +| | mean CPU-s / 120 s | mean ms per scan | +| --- | --- | --- | +| before | 2.207 | 1.837 | +| after | 2.200 | 1.831 | + +−0.3% against a ±0.7% within-group spread: no effect, as expected from flags +that barely moved. Also worth knowing: the whole container costs **1.8% of one +core**, so it is not where a nav mission's load goes. + +## `-ffp-contract=off` survives Release + +`odom_tf_relay`'s bit-identity against the Python it replaced depends on that +flag (`mote_nav/CMakeLists.txt:60`), and Release now stacks `-O3` over it. +Target options are emitted last, so it still wins — counted in the shipped +aarch64 objects, not inferred: `libodom_tf_relay_component.so` holds **0** fused +multiply-adds before and after, while `libicp_odom_gate_component.so` (same +`-O3`, no flag) holds **11** both times, so the zero is the flag and not a +target without FMA. `test_odom_tf_relay.cpp` cannot catch this — it compares the +relay against reference arithmetic in the same TU, under the same flag. diff --git a/mkdocs.yml b/mkdocs.yml index d8b94ef..220a2df 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -157,3 +157,4 @@ nav: - Camera layer decay: tuning/2026-07-29-camera-layer-decay.md - Monitor-node CPU: tuning/2026-08-11-monitor-cpu.md - Orientation picking: tuning/2026-08-11-orientation-picking.md + - C++ build type: tuning/2026-08-11-cmake-build-type.md diff --git a/pixi.toml b/pixi.toml index f9934e2..d3d73a5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -7,7 +7,10 @@ version = "0.1.0" [tasks] submodules = "git submodule update --init" -build = { cmd = "colcon build --symlink-install --cmake-args -G Ninja -DCMAKE_POLICY_VERSION_MINIMUM=3.5", depends-on = [ +# Release, so optimisation is stated here rather than inherited from whatever the +# conda activation puts in CXXFLAGS. NOT RelWithDebInfo: config flags land after +# CXXFLAGS, so its -O2 would lower the Pi's -O3. See docs/tuning/2026-08-11. +build = { cmd = "colcon build --symlink-install --cmake-args -G Ninja -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release", depends-on = [ "submodules", ] } test = { cmd = "colcon test --packages-select mote_hardware mote_bringup mote_nav mote_perception mote_tasks mote_arm mote_fleet --event-handlers console_direct+ && colcon test-result --verbose", depends-on = [