我正在编写一个3d矢量数学库,用于具有约束内存的嵌入式系统。大多数C数学库使用“输出参数”习惯,我非常不喜欢。在听到像Chandler Carruth这样的人谈论编译器如何非常善于优化传递和价值回报之后,我变得忠实于它是使用3个浮点数结构的可行选择。 (在我的用例中,所有函数都在头文件中内联,因此保留调用约定无关紧要。)
但是,在编写我的库并将其用于预期的应用程序之后,我发现堆栈的使用率非常高。通过切换回旧的“输出参数”样式,甚至更糟糕的是就地修改,我可以节省许多字节的堆栈空间。
此示例代码演示了此问题:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct vec {
float x; float y; float z;
} vec;
inline float vdot(vec a, vec b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline float vdotp(vec const *a, vec const *b) {
return a->x * b->x + a->y * b->y + a->z * b->z;
}
inline vec vscale(float s, vec a) {
vec v = { .x = s * a.x, .y = s * a.y, .z = s * a.z };
return v;
}
inline void vscalep(float s, vec *a) {
a->x = s * a->x;
a->y = s * a->y;
a->z = s * a->z;
}
inline vec vnormalize(vec a) {
float s = 1.0f / sqrtf(vdot(a, a));
return vscale(s, a);
}
inline void vnormalizep(vec *a) {
float s = 1.0f / sqrtf(vdotp(a, a));
vscalep(s, a);
}
inline vec vproject(vec a, vec b) {
vec bnorm = vnormalize(b);
float s = vdot(a, bnorm);
return vscale(s, bnorm);
}
inline void vprojectp(vec *a, vec const *b) {
vec bnorm = *b;
vnormalizep(&bnorm);
float s = vdotp(a, &bnorm);
vscalep(s, &bnorm);
*a = bnorm;
}
int main() {
vec a = { .x = rand(), .y = rand(), .z = rand() };
vec b = { .x = rand(), .y = rand(), .z = rand() };
#ifdef PTRS
vprojectp(&a, &b);
#else
a = vproject(a, b);
#endif
printf("a: %f, %f, %f\n", a.x, a.y, a.z);
}
我希望,在同一个文件中标记为inline
的所有函数的情况下,编译器将能够将我的按值传递代码“解构”为其核心标量数学运算,并大致生成两个版本的堆栈大小相同。但是,它不能:
> gcc -O3 -fconserve-stack -Wstack-usage=30 main.c -lm
main.c: In function ‘main’:
main.c:52:6: warning: stack usage is 64 bytes [-Wstack-usage=]
而使用就地版本:
> gcc -O3 -fconserve-stack -Wstack-usage=30 -DPTRS main.c -lm
main.c: In function ‘main’:
main.c:52:6: warning: stack usage is 48 bytes [-Wstack-usage=]
在我的实际应用中,我在更大范围内看到了同样的问题。通过值传递在堆栈空间中耗费了数百个字节。
在这种情况下,有没有办法获得更好的堆栈使用优化?我已经在使用fconserve-stack
了。我愿意花费任何额外的优化时间来获得更好的结果。
(我在ARM上使用GCC 4.9,但在x86-64上也出现了同样的问题。我的示例代码中的堆栈大小是x86-64。)