4. 循环倒置
任务目标
在嵌入式循环中,若每次迭代都执行与循环无关的条件判断,分支预测器会因结果不稳定而产生惩罚。本案例通过循环倒置(将条件判断移出循环体)消除冗余分支,以时间换空间效率。目标:给定一组传感器数据,统计其中超过阈值的个数,并计算所有数据的平均值——但阈值判断与平均值计算互不依赖,因此可拆分为两个独立循环,使每个循环内部无分支。
核心实现
#include <stdint.h>
#define N 128
// 原始版本:单循环内包含分支
uint32_t count_over_threshold_v1(const int16_t *data, int16_t threshold) {
uint32_t cnt = 0;
for (int i = 0; i < N; i++) {
if (data[i] > threshold) { // 每次迭代都判断
cnt++;
}
}
return cnt;
}
// 循环倒置版本:先统计,再累加,循环内无分支
uint32_t count_over_threshold_v2(const int16_t *data, int16_t threshold) {
uint32_t cnt = 0;
// 第一遍:只做比较,无分支(利用比较结果直接加0或1)
for (int i = 0; i < N; i++) {
cnt += (data[i] > threshold) ? 1u : 0u;
}
return cnt;
}
// 若需同时计算平均值,可进一步拆分:
int32_t sum_all(const int16_t *data) {
int32_t sum = 0;
for (int i = 0; i < N; i++) {
sum += data[i]; // 无分支
}
return sum;
}
关键说明
上述v2版本通过算术化分支(将if转换为三元运算符)使编译器可生成无跳转的SETcc指令或条件移动,从而避免分支预测失败。若目标平台支持__builtin_expect或SIMD,可进一步优化,但此处保持最小可复现实现。
运行与验证
// 测试代码(需包含上述函数)
#include <stdio.h>
#include <assert.h>
int main() {
int16_t data[N];
for (int i = 0; i < N; i++) {
data[i] = (int16_t)(i * 3 - 100); // 确定性输入
}
int16_t th = 50;
uint32_t c1 = count_over_threshold_v1(data, th);
uint32_t c2 = count_over_threshold_v2(data, th);
assert(c1 == c2); // 功能等价验证
printf("v1=%u, v2=%u, 结果一致\n", c1, c2);
return 0;
}
编译与运行:
gcc -O2 -o loop_invert loop_invert.c && ./loop_invert
通过条件:程序输出v1=26, v2=26(因i*3-100>50即i>50,共128-51=77个?实际计算:i=51时值为53>50,故从51到127共77个)。预期输出:v1=77, v2=77, 结果一致。若需观察分支优化效果,可反汇编对比v1与v2的循环体指令数。
性能对比方法
在目标嵌入式平台(如Cortex-M4)上,可用DWT->CYCCNT寄存器测量两版本执行周期数。通常v2比v1少约20%~30%周期(取决于数据分布)。但本案例不提供实测数据,仅说明测量方法:初始化DWT后,分别对两函数运行1000次取平均周期差。
注意事项:循环倒置适用于循环次数已知且较大的场景。若循环体本身极短(如仅2~3条指令),分支开销占比高,收益明显;若循环次数少(<5),则可能因代码体积增加而得不偿失。本案例中N=128为典型嵌入式传感器批次大小。