
1. 1kHz 控制环里的小热点
三角函数、畸变系数、CRC 表在运行时每帧算一遍——单看 profiler 是小热点,1kHz 控制环里会积少成多。constexpr 把不变计算挪到编译期,ROM 换 CPU,表放 .rodata 多进程共享 page cache。constexpr 不是炫技——是把「已知不变」从 hot path 挪走;嵌入式 flash 受限时要同时看 binary 体积与 icache,表太大反而 miss 更多。LUT 边界 static_assert;YAML 读表不走 constexpr——各司其职,混用会导致编译失败或静默 runtime 回退,改 C++ 标准后要重查表生成路径。
2. 编译期表
constexpr auto makeTable() {
std::array<float, 360> t{};
for (int i = 0; i < 360; ++i)
t[i] = std::sin(i * std::numbers::pi_v<float> / 180.f);
return t;
}
static constexpr auto kSin = makeTable();
float fast_sin_deg(int deg) {
return kSin[std::clamp(deg % 360, 0, 359)];
}C++20 起 loop 在 constexpr 函数里更自由;C++17 用 constexpr lambda 立即调用生成表。static_assert 锁边界;单测对比 std::sin reference,max err 写进 spec。consteval 生成 CRC 多项式表,防 runtime 初始化顺序问题。compile-time 与 runtime 表 diff 应 zero,防手改表未同步。
3. 何时不值得
表太大撑爆 I-cache(64KB 级要小心)——过大表反而 miss,分 tier(小角线性插值)有时更优。输入连续 float 而非索引——插值仍要 runtime,LUT 省不了主成本。精度要求高于 float 表——用 double 或仍 call libm。读 YAML、查 map 不能 constexpr——物理常数、CRC、单位换算放编译期;运行时配置仍走普通初始化。C++14 非 constexpr 路径可能静默 runtime 回退,C++20 更 strict,改标准后要重查表生成路径。constexpr 的价值在「全团队同一张表、编译期锁边界」,不是把运行时 YAML 硬塞进 consteval。
4. 机器人例子
轮速里程计:steering 角离散 0.1° 一档,预计算 sin/cos。镜头 undistort:固定分辨率下 uv 映射双线性索引表,启动时 load 一次。MCU 与 host 共用同一 header 的 CRC 表,避免双份逻辑漂移。is_constant_evaluated 同一函数 host 编译期与 device 运行时分支——onboard 少用,单元测试里 constexpr 路径可覆盖表边界。固定分辨率 undistort 表在标定更新后需整表重算——constexpr 生成一次、运行时 load 与「每帧算畸变」的取舍要在 spec 里写清误差上限。
5. 索引与单位陷阱
表按 degree 索引但传入 radian——系统性角度错,索引单位写进 API 文档。LUT 索引 clamp 防越界;快速 atan2 近似用四千零九十六项表,误差 budget 写进 spec。改表后 golden vector max err < 1e-4 rad,超阈值 static_assert 阻 CI。表边界 static_assert 单调性与端点值。索引单位应在类型名或函数名里体现(如 sin_deg vs sin_rad),靠注释 alone 拦不住 call site 传错。
6. 体积与 cache
LUT 进 .rodata,flash 受限 MCU 要测 binary 体积;host 侧关注 icache——过大表反而 miss。多进程共享 page cache,但首次 touch 仍占带宽,startup 别重复生成同一张表。分 tier:小角线性插值加小表,有时比单张大表更优。改表后 golden vector 与 libm reference 对比,max err 超阈值则 CI 失败——ROM 换 CPU 的前提是误差 budget 可审计,不能靠「感觉够准」上 field。
7. 案例:度弧度混用导致转向系统性偏
某底盘用 constexpr sin 表,API 文档写 degree,控制环传入 radian——field 里转向系统性偏零点五度,仿真因巧合未暴露。改成 API 强制 degree 并 static_assert 端点值后消失。根因是索引单位契约缺失,不是表精度。教训:索引单位在 API、文档、测试三重标注;compile-time 与 runtime 表 diff zero。改表后必须跑 golden vector,max err 超阈值则 static_assert 阻 CI,别靠肉眼扫表。
8. 验收
- 全表 max error 对比 reference,CI 失败阻合入。
- 索引单位(度/弧度)文档与测试双重标注。
- MCU 测 binary 体积;host 测 icache。
- YAML 配置不走 constexpr 路径。
- 表边界单调性与端点 static_assert。
hot path 才 ROM 换 CPU;配置仍 runtime——别把读参塞进 constexpr 函数。LUT 与 libm 混用时,误差 budget 写进 spec,field 不接受「差不多」。改表体积或精度时同时测 icache 与 max error,二者缺一都可能让优化反噬;索引单位应在函数名体现,靠注释 alone 拦不住 call site 传错,CI 应阻合入超阈值误差。
相关
也可以看看
johan's blog