加速AVR函数指针

时间:2014-09-28 12:30:01

标签: arduino avr atmega

我有一个avr程序,我想使用指向方法的指针。但为什么使用函数指针超过正常调用几乎慢4倍?我该如何加快速度呢?

我有:

void simple_call(){ PORTB |= _BV(1); }

void (*simple)() = &simple_call; 

然后如果我用-O3编译并调用:

simple_call() 

完成需要250ns。如果我改为致电:

simple()

需要960ns才能完成!!

如何让它更快?

1 个答案:

答案 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个时钟。除了最琐碎的功能外,没关系。

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