加速AVR函数指针

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!

我怎样才能让它更快?

jdr*_*5ca 7

为什么慢?

您看到时间增加了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个时钟周期.除了最琐碎的功能外,没关系.

你不能:即使你试图内联函数,你仍然需要一个条件分支.一堆加载,比较和条件跳转将比间接调用花费更多.换句话说,函数指针是一种比任何条件更好的分支方法.