Embedded 嵌入式系统中的函数指针有用吗?
在一次采访中,他们问我在为嵌入式系统编写代码时,使用函数指针是否有益(就速度而言)?我对嵌入式系统一无所知,所以无法回答这个问题。只是含糊不清的回答。Embedded 嵌入式系统中的函数指针有用吗?,embedded,functor,Embedded,Functor,在一次采访中,他们问我在为嵌入式系统编写代码时,使用函数指针是否有益(就速度而言)?我对嵌入式系统一无所知,所以无法回答这个问题。只是含糊不清的回答。 那么真正的好处是什么呢?速度、可读性、维护、成本?我会说,它们在任何环境中都是有益的(就速度而言),而不仅仅是嵌入式环境。其思想是,一旦指针指向正确的函数,就不需要进一步的决策逻辑来调用该函数。让我们看看 速度(假设我们在手臂上):然后(理论上): (正常功能调用ARM指令大小)
那么真正的好处是什么呢?速度、可读性、维护、成本?我会说,它们在任何环境中都是有益的(就速度而言),而不仅仅是嵌入式环境。其思想是,一旦指针指向正确的函数,就不需要进一步的决策逻辑来调用该函数。让我们看看 速度(假设我们在手臂上):然后(理论上): (正常功能调用ARM指令大小)<(功能指针调用设置指令大小) 由于它们是设置函数指针调用的附加间接层,因此将涉及附加的ARM指令 PS:普通函数调用:使用BL设置的函数调用
PSS:不知道它们的实际尺寸,但应该很容易验证 函数指针的一个消极方面是它们永远不会在调用站点内联。这可能有益,也可能有害,这取决于编译速度或大小。如果是后者,则它们应该与普通函数调用没有区别。函数指针的另一个缺点(对于虚拟函数,因为它们只是核心级别的函数指针): 使函数内联&虚拟将强制编译器创建同一函数的脱机副本。这将增加最终二进制文件的大小(假设大量使用它)
经验法则:1:不要内联进行虚拟呼叫你在速度上有所提高,但在可读性和维护上有所损失。而不是一个if-then-else树,if-a-then-fun\u a(),else-if-b-then-fun\u b(),else-if-c-then-fun\u-default(),每次都要这样做,如果a-then-fun=fun\u-a,else-if-b-then-fun=fun\u-b,等等,然后你做一次,从那时起,只要调用fun()。快得多。正如前面指出的,不能内联,这是另一个速度技巧,但是if-then-else树上的内联并不一定比没有内联的树快,而且通常没有函数指针快 你会失去一点可读性和可维护性,因为你必须弄清楚fun()的设置位置,它的变化频率(如果有),确保在设置之前不调用它,但它仍然是一个可搜索的名称,你可以使用它来查找和维护所有使用它的地方 这基本上是一种速度技巧,可以在每次执行函数时避免if-then-else树。如果性能不重要,如果没有其他功能,则fun()可以是静态的,并在其中包含If-then-else树 编辑并添加一些示例来解释我所说的内容 extern unsigned int fun1 ( unsigned int a, unsigned int b ); unsigned int (*funptr)(unsigned int, unsigned int); void have_fun ( unsigned int x, unsigned int y, unsigned int z ) { unsigned int j; funptr=fun1; j=fun1(z,5); j=funptr(y,6); } 外部无符号整数fun1(无符号整数a,无符号整数b); 无符号整数(*funptr)(无符号整数,无符号整数); void have__fun(无符号整数x、无符号整数y、无符号整数z) { 无符号整数j; funptr=fun1; j=fun1(z,5); j=funptr(y,6); } 编译给出了以下结果: have_fun: stmfd sp!, {r3, r4, r5, lr} .save {r3, r4, r5, lr} ldr r4, .L2 mov r5, r1 mov r0, r2 mov r1, #5 ldr r2, .L2+4 str r2, [r4, #0] bl fun1 ldr r3, [r4, #0] mov r0, r5 mov r1, #6 blx r3 ldmfd sp!, {r3, r4, r5, pc} 玩得开心: stmfd sp!,{r3,r4,r5,lr} .save{r3,r4,r5,lr} ldr r4,.L2 mov r5,r1 mov r0,r2 第1节,第5节 ldr r2、.L2+4 str r2[r4,#0] bl fun1 ldr r3,[r4,#0] mov r0,r5 第1节,第6节 blx r3 ldmfd sp!,{r3,r4,r5,pc} 我想Clifford所说的是一个直接的电话 一条指令就足够了(取决于架构) bl fun1 bl fun1 其中一个函数指针至少要花费两个 ldr r3, [r4, #0] blx r3 ldr r3,[r4,#0] blx r3 我还提到了直接和间接的区别 你承受的额外负担 在继续之前,值得一提的是内联的优点和缺点。 对于这些示例所使用的ARM,调用 约定使用r0-r3作为函数的传入参数,并使用r0 返回。因此,使用三个参数进入have_fun()表示r0-r3 有内容。对于ARM,还假设函数可以破坏 r0-r3,have_fun()需要保留输入,然后放置 r0和r1中fun1()的两个输入,因此发生了一点寄存器舞蹈 mov r5, r1 mov r0, r2 mov r1, #5 ldr r2, .L2+4 str r2, [r4, #0] bl fun1 mov r5,r1 mov r0,r2 第1节,第5节 ldr r2、.L2+4 str r2[r4,#0] bl fun1 编译器非常聪明,可以看出我们从不需要第一个 输入have_fun()函数,因此r0被丢弃并允许 马上换。而且,编译器也足够聪明,知道 在发送之后,我们永远不需要第三个参数z(r2) 它在第一次调用时被转换为fun1(),因此它不需要将其保存在高位 登记R1但是,第二个参数需要 被保留,以便将其放入一个不会被fun1()破坏的regsiter中 您可以看到第二个函数调用也会发生同样的情况 假设fun1()是一个简单的函数: inline unsigned int fun1 ( unsigned int a, unsigned int b ) { return(a+b); } 内联无符号整数fun1(无符号整数a、无符号整数b) { 返回(a+b); } 当您内联fun1()时,会得到如下结果: stmfd sp!, {r4, lr} mov r0, r1 mov r1, #6 add r4, r2, #5
typedef struct
{
uint16_t counter;
void (*callback)(void);
} Timer_t;
stmfd sp!,{r4,lr}
mov r0,r1
第1节,第6节
加上r4、r2和#5
编译器不需要洗牌即将开始的较低寄存器
准备打电话。同样,您可能已经注意到r4和
当我们输入hello_fun()时,lr将保留在堆栈上。用这个
ARM调用约定函数可以破坏r0-r3,但必须保留
所有其他寄存器,因为本例中的have_fun()需要更多
四个寄存器完成它的工作,它将r4的内容保存在
堆叠以便它可以使用它。同样,这个函数也是我编译的
它确实调用了另一个函数,bl/blx指令使用/销毁
lr寄存器(r14),所以为了让have_fun()返回,我们还需要
在堆栈上保留lr。fun1()的简化示例没有
没有显示这一点,但是从内联中得到的另一个节省是在en上
if(fputype==FPU_FPA) fdivide=fdivide_fpa;
else if(fputype==FPU_VFP) fdivide=fdivide_vfp;
else fdivide=fdivide_soft;
if(fputype==FPU_FPA) fdivide=fdivide_fpa;
else if(fputype==FPU_VFP) fdivide=fdivide_vfp;
else fdivide=fdivide_soft;
a=fdivide(b,c);
if(fputype==FPU_FPA) a=fdivide_fpa(b,c);
else if(fputype==FPU_VFP) a=fdivide_vfp(b,c);
else a=fdivide_soft(b,c);
unsigned int fun1 ( unsigned int a, unsigned int b );
unsigned int fun2 ( unsigned int a, unsigned int b );
unsigned int fun3 ( unsigned int a, unsigned int b );
unsigned int (*funptr)(unsigned int, unsigned int);
unsigned int have_fun ( unsigned int x, unsigned int y, unsigned int z )
{
unsigned int j;
switch(x)
{
default:
case 1: j=fun1(y,z); break;
case 2: j=fun2(y,z); break;
case 3: j=fun3(y,z); break;
}
return(j);
}
unsigned int more_fun ( unsigned int x, unsigned int y, unsigned int z )
{
unsigned int j;
j=funptr(y,z);
return(j);
}
cmp r0, #2
beq .L3
cmp r0, #3
beq .L4
mov r0, r1
mov r1, r2
b fun1
.L3:
mov r0, r1
mov r1, r2
b fun2
.L4:
mov r0, r1
mov r1, r2
b fun3
mov r0, r1
ldr r3, .L7
mov r1, r2
blx r3
unsigned int fun1 ( unsigned int a, unsigned int b )
{
return(a+b);
}
unsigned int fun2 ( unsigned int a, unsigned int b )
{
return(a-b);
}
unsigned int fun3 ( unsigned int a, unsigned int b )
{
return(a&b);
}
unsigned int have_fun ( unsigned int x, unsigned int y, unsigned int z )
{
unsigned int j;
switch(x)
{
default:
case 1: j=fun1(y,z); break;
case 2: j=fun2(y,z); break;
case 3: j=fun3(y,z); break;
}
return(j);
}
have_fun:
cmp r0, #2
rsbeq r0, r2, r1
bxeq lr
cmp r0, #3
addne r0, r2, r1
andeq r0, r2, r1
bx lr
cmp r0, #2
beq .L3
cmp r0, #3
beq .L4
and r0,r1,r2
bx lr
.L3:
sub r0,r1,r2
bx lr
.L4:
add r0,r1,r2
bx lr
mov r0, r1
ldr r1, .L7
ldr r3,[r1]
mov r1, r2
blx r3
typedef struct
{
uint16_t counter;
void (*callback)(void);
} Timer_t;