我有一个avr程序,我想使用指向方法的指针。但为什么使用函数指针超过正常调用几乎慢4倍?我该如何加快速度呢?
我有:
void simple_call(){ PORTB |= _BV(1); }
void (*simple)() = &simple_call;
然后如果我用-O3编译并调用:
simple_call()
完成需要250ns。如果我改为致电:
simple()
需要960ns才能完成!!
如何让它更快?
答案 0 :(得分:7)
为什么它会慢?
您看到时间增加了710 ns。对于16 MHz时钟,该时间为11个时钟。
说4X是不公平的,因为时间增加是函数指针的恒定开销。在您的情况下,函数体很小,因此开销相对较大。但是如果你有一个函数很大并且需要1毫秒才能执行的情况,那么时间增加仍然是710 ns,你会问为什么函数指针的时间会延长0.07%?
要了解为什么一种方法比另一种方法更快,您需要获得汇编代码。使用Eclipse之类的构建工具允许您通过添加Arduino IDE不具备的命令行选项从GCC编译器获取汇编程序列表。这对于弄清楚发生了什么是非常宝贵的。
以下是汇编程序列表的一部分,显示了您的想法:
simple_call();
308: 0e 94 32 01 call 0x264 ; 0x264 <_Z11simple_callv>
simple();
30c: e0 91 0a 02 lds r30, 0x020A
310: f0 91 0b 02 lds r31, 0x020B
314: 19 95 eicall
这些清单显示了编译器生成的源代码和汇编程序。为了理解这一点并计算出时序,你需要Atmel AVR instruction reference,其中包含每条指令的描述和它们所采用的时钟周期数。 simple_call()可能是您所期望的并且需要4个滴答。 simple()表示:
LDS = load address byte - 2 ticks
LDS = load address byte - 2 ticks
EICALL = indirect call to address loaded - 4 ticks
这两个都调用函数simple_call():
void simple_call(){ PORTB |= _BV(1); }
264: df 93 push r29
266: cf 93 push r28
268: cd b7 in r28, 0x3d ; 61
26a: de b7 in r29, 0x3e ; 62
26c: a5 e2 ldi r26, 0x25 ; 37
26e: b0 e0 ldi r27, 0x00 ; 0
270: e5 e2 ldi r30, 0x25 ; 37
272: f0 e0 ldi r31, 0x00 ; 0
274: 80 81 ld r24, Z
276: 82 60 ori r24, 0x02 ; 2
278: 8c 93 st X, r24
27a: cf 91 pop r28
27c: df 91 pop r29
27e: 08 95 ret
因此,与函数方法中的所有指令相比,函数指针应该只需要4个刻度并且很小。
上面,我说应该和你认为发生了什么。我撒了一点:上面的汇编程序没有优化。
您使用了优化 -O3 来更改所有内容。
通过优化,函数体被压缩到几乎没有:
void simple_call(){ PORTB |= _BV(1); }
264: 29 9a sbi 0x05, 1 ; 5
266: 08 95 ret
那是2 + 4滴答。编译器大师已经编译了编译器,以找出一种更好的方法来执行一行C ++。但等等还有更多。当你&#34;打电话&#34;编译器说你的功能&#34;为什么这样做?它只是一个汇编指令&#34;。编译器决定你的调用毫无意义,并将指令内联:
void simple_call(){ PORTB |= _BV(1); }
2d6: 29 9a sbi 0x05, 1 ; 5
但是通过优化,函数指针调用仍然是一个调用:
simple();
2d8: e0 91 0a 02 lds r30, 0x020A
2dc: f0 91 0b 02 lds r31, 0x020B
2e0: 19 95 eicall
让我们看看数学是否加起来。使用内联,&#34;调用&#34;是3蜱。间接呼叫是8 + 6 = 14.差异是11个滴答! (我可以补充!)
这就是**为什么*。
如何加快速度?
您不需要:进行函数指针调用仅需4个时钟。除了最琐碎的功能外,没关系。
你不能:即使你试图内联函数,你仍然需要一个条件分支。一堆加载,比较和条件跳转将比间接调用花费更多。换句话说,函数指针是一种比任何条件更好的分支方法。