Arduino 加速AVR函数指针

Arduino 加速AVR函数指针,arduino,avr,atmega,Arduino,Avr,Atmega,我有一个avr的程序,我想使用一个指向方法的指针。但是为什么使用函数指针比普通调用慢4倍呢??我该如何加速呢 我有: void simple_call(){ PORTB |= _BV(1); } void (*simple)() = &simple_call; 然后,如果我使用-O3编译并调用: simple_call() 需要250纳秒才能完成。如果我改打电话: simple() 需要960ns才能完成 我怎样才能使它更快 为什么它慢了 你可以看到时间增加了710纳秒。对于

我有一个avr的程序,我想使用一个指向方法的指针。但是为什么使用函数指针比普通调用慢4倍呢??我该如何加速呢

我有:

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

void (*simple)() = &simple_call; 
然后,如果我使用-O3编译并调用:

simple_call() 
需要250纳秒才能完成。如果我改打电话:

simple()
需要960ns才能完成

我怎样才能使它更快

为什么它慢了

你可以看到时间增加了710纳秒。对于一个16兆赫的时钟,这个时间是11个滴答声

说4X是不公平的,因为时间的增加对于函数指针来说是一个恒定的开销。在您的例子中,函数体很小,因此开销相对较大。但是,如果函数很大,执行时间为1ms,那么时间增加仍然是710ns,并且您会问为什么函数指针要多花费0.07%的时间

要了解为什么一种方法比另一种方法快,您需要了解汇编代码。使用Eclipse之类的构建工具,您可以通过添加ArduinoIDE中不可用的命令行选项,从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
它们都调用函数simple\u call():

因此,与函数方法中的所有指令相比,函数指针应该只多取4个刻度,并且应该很小


上面,我说应该你认为正在发生的事情。我撒了一点谎:上面的汇编程序不支持优化

您使用的优化-O3改变了一切

通过优化,函数体几乎被压缩为零:

void simple_call(){ PORTB |= _BV(1); }
 264:   29 9a           sbi 0x05, 1 ; 5
 266:   08 95           ret
这是2+4个刻度。编译器GurUS已经对编译器进行编码,以找出执行C++一行的更好方法。但是等等,还有更多。当你“调用”你的函数时,编译器会说“为什么这么做?它只是一条汇编指令”。编译器确定您的调用没有意义,并将指令内联:

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
那么,让我们看看数学是否正确。对于内联,“调用”是3个刻度。间接调用是8+6=14。差别是11分!(我可以补充!)

这就是为什么

我如何加速它

您不需要:进行函数指针调用只需要4个时钟点。除了最琐碎的函数外,这并不重要

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

为什么它慢了

你可以看到时间增加了710纳秒。对于一个16兆赫的时钟,这个时间是11个滴答声

说4X是不公平的,因为时间的增加对于函数指针来说是一个恒定的开销。在您的例子中,函数体很小,因此开销相对较大。但是,如果函数很大,执行时间为1ms,那么时间增加仍然是710ns,并且您会问为什么函数指针要多花费0.07%的时间

要了解为什么一种方法比另一种方法快,您需要了解汇编代码。使用Eclipse之类的构建工具,您可以通过添加ArduinoIDE中不可用的命令行选项,从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
它们都调用函数simple\u call():

因此,与函数方法中的所有指令相比,函数指针应该只多取4个刻度,并且应该很小


上面,我说应该你认为正在发生的事情。我撒了一点谎:上面的汇编程序不支持优化

您使用的优化-O3改变了一切

通过优化,函数体几乎被压缩为零:

void simple_call(){ PORTB |= _BV(1); }
 264:   29 9a           sbi 0x05, 1 ; 5
 266:   08 95           ret
这是2+4个刻度。编译器GurUS已经对编译器进行编码,以找出执行C++一行的更好方法。但是等等,还有更多。当你“调用”你的函数时,编译器会说“为什么这么做?它只是一条汇编指令”。编译器确定您的调用没有意义,并将指令内联:

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
那么,让我们看看数学是否正确。对于内联,“调用”是3个刻度。间接调用是8+6=14。差别是11分!(我可以补充!)

这就是为什么

我如何加速它

您不需要:进行函数指针调用只需要4个时钟点。除了最琐碎的函数外,这并不重要

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