如何使用标量数组初始化含标量成员的cpx结构体数组以节省内存占用?
结论
可以实现,只要满足两个前提即可:
- 编译器不会给
cpx结构体插入填充字节:这个对kissFFT定义的复数结构体100%成立,所有主流编译器对两个连续float成员的结构体都不会加填充,你可以加编译期断言确认。 - 遵循C语言严格别名规则,避免未定义行为,不要直接强转指针,建议用union做类型双关或者开启
-fno-strict-aliasing编译选项(嵌入式开发常用)。
具体实现方案
方案1:x和y内存连续的场景(上层调用时x、y从同一块连续缓冲区分配)
这种场景下x+y的总大小刚好等于N个cpx结构体的大小,可以直接复用作为库函数的输出数组,完全砍掉原来的tmp_cpx_B临时数组,节省7680字节内存:
// 编译期校验结构体布局合法性 static_assert(sizeof(cpx) == 2 * sizeof(float), "cpx结构体存在填充字节"); static_assert(offsetof(cpx, real) == 0, "real成员偏移量错误"); static_assert(offsetof(cpx, imag) == sizeof(float), "imag成员偏移量错误"); void my_func(float *x, float *y) /* 要求x和y指向连续内存,总大小2*960*sizeof(float) */ { cpx tmp_cpx_A[960]; // 填充tmp_cpx_A的逻辑保持不变 for (int i = 0; i < 960; i++) { tmp_cpx_A[i].real = do_some_calculation(x[i]); tmp_cpx_A[i].imag = do_some_other_calculation(x[i]); } // 用union绕开严格别名规则,将x+y的连续内存转为cpx数组使用 union { float *f; cpx *c; } alias; alias.f = x; some_library_function(tmp_cpx_A, alias.c); // 输出直接写入x+y的内存 // 从复用的内存中读取结果计算输出y for (int i = 0; i < 960; i++) { y[i] = do_final_calculation(alias.c[i].real, alias.c[i].imag); } }
如果你的项目编译时开启了-fno-strict-aliasing选项,可以省略union,直接强转指针:
cpx *tmp_cpx_B = (cpx*)x; some_library_function(tmp_cpx_A, tmp_cpx_B);
方案2:x和y内存不连续的场景
这种场景下可以砍掉一半临时内存占用,只保留1个cpx临时数组,通过两次轻量循环交换数据,总循环次数仅1920次,性能损耗可以忽略:
static_assert(sizeof(cpx) == 2 * sizeof(float), "cpx结构体存在填充字节"); void my_func(float *x, float *y) { cpx tmp_buf[960]; // 仅保留一个临时数组,节省7680字节 // 用输入x生成库函数需要的输入数组 for (int i = 0; i < 960; i++) { tmp_buf[i].real = do_some_calculation(x[i]); tmp_buf[i].imag = do_some_other_calculation(x[i]); } // 把输入数组的实部、虚部分别存入已无用的x数组和未使用的y数组 for (int i = 0; i < 960; i++) { x[i] = tmp_buf[i].real; y[i] = tmp_buf[i].imag; } // 重新组装输入数组,调用库函数输出到tmp_buf for (int i = 0; i < 960; i++) { tmp_buf[i].real = x[i]; tmp_buf[i].imag = y[i]; } // 若库函数支持原地操作直接传同一个指针即可,不支持的话可调整为分块读写避免重叠 some_library_function(tmp_buf, tmp_buf); // 计算最终输出y for (int i = 0; i < 960; i++) { y[i] = do_final_calculation(tmp_buf[i].real, tmp_buf[i].imag); } }
注意事项
kissFFT的kiss_fft_cpx结构体设计本身就保证了内存连续无填充,上述方案在所有嵌入式常用编译器(GCC、ARMCC、IAR)下都可以正常运行,不会出现兼容性问题,也不会暴露内部依赖,对外API保持不变。
内容的提问来源于stack exchange,提问作者ZinGer_KyoN
相关产品推荐
相关产品推荐

