use*_*084 1 avr atmega arduino
我有一个avr程序,我想使用指向方法的指针.但为什么使用函数指针超过正常调用几乎慢4倍?我该如何加快速度呢?
我有:
void simple_call(){ PORTB |= _BV(1); }
void (*simple)() = &simple_call;
Run Code Online (Sandbox Code Playgroud)
然后如果我用-O3编译并调用:
simple_call()
Run Code Online (Sandbox Code Playgroud)
完成需要250ns.如果我改为打电话:
simple()
Run Code Online (Sandbox Code Playgroud)
完成需要960ns!
我怎样才能让它更快?
为什么慢?
您看到时间增加了710 ns.对于16 MHz时钟,该时间为11个时钟.
说4X是不公平的,因为时间增加是函数指针的恒定开销.在您的情况下,函数体很小,因此开销相对较大.但是如果你有一个函数很大并且执行时间为1毫秒的情况,那么时间增加仍然是710 ns,你会问为什么函数指针需要更长的时间?
要了解为什么一种方法比另一种方法更快,您需要获得汇编代码.使用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
Run Code Online (Sandbox Code Playgroud)
这些清单显示了编译器生成的源代码和汇编程序.要理解这一点并计算出时序,您需要Atmel AVR指令参考,其中包含每条指令的描述以及它们所采用的时钟周期数.simple_call()可能是您所期望的并且需要4个滴答.simple()说:
LDS = load address byte - 2 ticks
LDS = load address byte - 2 ticks
EICALL = indirect call to address loaded - 4 ticks
Run Code Online (Sandbox Code Playgroud)
那些都调用函数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
Run Code Online (Sandbox Code Playgroud)
所以函数指针应该只需4个刻度,并且与函数方法中的所有指令相比较小.
在上面,我说应该和你认为发生了什么.我撒了一点:上面的汇编程序是没有优化的.
您使用了优化-O3来改变一切.
通过优化,函数体被压缩到几乎没有:
void simple_call(){ PORTB |= _BV(1); }
264: 29 9a sbi 0x05, 1 ; 5
266: 08 95 ret
Run Code Online (Sandbox Code Playgroud)
那是2 + 4滴答.编译器大师已经编译了编译器,以找出一种更好的方法来执行一行C++.但等等还有更多.当你"调用"你的函数时,编译器会说"为什么这样做?它只是一个汇编指令".编译器决定您的调用是无意义的并将指令内联:
void simple_call(){ PORTB |= _BV(1); }
2d6: 29 9a sbi 0x05, 1 ; 5
Run Code Online (Sandbox Code Playgroud)
但是通过优化,函数指针调用仍然是一个调用:
simple();
2d8: e0 91 0a 02 lds r30, 0x020A
2dc: f0 91 0b 02 lds r31, 0x020B
2e0: 19 95 eicall
Run Code Online (Sandbox Code Playgroud)
所以让我们看看数学是否相加.使用内联,"调用"是3个刻度.间接呼叫是8 + 6 = 14.差异是11个滴答!(我可以加!)
这就是**为什么*.
我该如何加快速度?
您不需要:进行函数指针调用只需要 4个时钟周期.除了最琐碎的功能外,没关系.
你不能:即使你试图内联函数,你仍然需要一个条件分支.一堆加载,比较和条件跳转将比间接调用花费更多.换句话说,函数指针是一种比任何条件更好的分支方法.