Arduino上数学运算的计时速度-异常

时间:2018-09-02 04:39:52

标签: c++ optimization arduino

我正在对Arduino Uno上的整数除法进行一些研究,并试图确定解决除法的最快方法。

在阅读中,我遇到了this个帖子。

我抓取并修改了尼克·加蒙(Nick Gammon)在回复#4中发布的草图,最终目的是编译我需要执行的计算及其时序的列表。

这是我修改后的草图:

#define DATATYPE byte
#define OPERATION i / j

volatile DATATYPE x=0;
volatile DATATYPE y=0;
//volatile DATATYPE j=7;
unsigned long time;
unsigned long loop_time;

void setup ()
{
    Serial.begin(115200);
    Serial.println ();

    time = micros();
    for (DATATYPE i = 1; i < 101; i++) {
        //for (DATATYPE j = 1; j < 101; j++) {
        for (DATATYPE j = 56; j < 57; j++) {
            y++;
        } // end of for j
    }  // end of for i
    loop_time = micros() - time;
    Serial.print("Loop baseline Completion time: ");
    Serial.print(loop_time);
    Serial.print(" ");

    time = micros();
    for (DATATYPE i = 1; i < 101; i++) {
        //for (DATATYPE j = 1; j < 101; j++) {
        for (DATATYPE j = 56; j < 57; j++) {
          y++;
            x = OPERATION;
        } // end of for j
    }  // end of for i
    time = micros() - time;
    Serial.print("Completion time: ");
    Serial.print(time);
    Serial.print(" Operation time: ");
    time = time - loop_time;
    Serial.println(time);
} // end of setup

void loop() {}

此输出为:

Loop baseline Completion time: 52 Completion time: 644 Operation time: 592

对于每个使用字节的除法运算,这实际上给出了5usec的时间。

但是,从更改两个循环:

for (DATATYPE j = 56; j < 57; j++) {

for (DATATYPE j = 57; j < 58; j++) {

给出以下输出:

Loop baseline Completion time: 52 Completion time: 108 Operation time: 56

我为内循环(7、56、58、3)等尝试了几种不同的值

3给出类似的结果,但7、11和13则不显示

有人可以告诉我为什么执行操作的时间会有如此大的差异吗?

1 个答案:

答案 0 :(得分:1)

如果编译器知道除法的分母是什么,则它可能能够通过乘以(乘分母的倒数)和一个移位来计算除法。由于除法运算的速度确实很慢,即使在具有除法指令的CPU上,这种技术(通常称为倒数乘法)也可以大获成功。

不幸的是,某些分母比其他分母更容易优化,尤其是在乘法限制为16位的情况下。因此,您可能会看到具有不同常数除数的不同行为。

我认为编译器没有看到可用于该循环的更好的优化,即将一个临时值设置为0并将其用作除法结果。当i达到56(或视情况而定为57)时,临时变量会增加。我认为这将从根本上减少执行时间。


编译测试

好奇心是我最好的,所以我安装了当前的Ubuntu avr-gcc软件包v5.4.0,并尝试了一些编译。我的程序比原始程序简单得多:

uint8_t dodiv(uint8_t d) {
  return d / DIVISOR;
}

然后我用如下命令编译它:

avr-gcc -S -Wall -O3 -o - -mmcu=atmega32 -DDIVISOR=57 avrdiv.c

据我所知,-mmcu=atmega32对于Arduino Uno是正确的。

以下是一些结果,仅限于dodiv函数的正文。我认为这并不能真正解释OP的测试结果在56到57之间的差异,但我可能会遗漏一些东西。这肯定说明了为什么3和7可能有非常不同的时间安排。

/* DIVISOR 3 */     /* DIVISOR 7 */     /* DIVISOR 56 */    /* DIVISOR 57 */
dodiv:              dodiv:              dodiv:              dodiv:
    ldi r25,lo8(-85)    ldi r25,lo8(37)     lsr r24             ldi r25,lo8(9)
    mul r24,r25         mul r24,r25         lsr r24             mul r24,r25
    mov r24,r1          mov r25,r1          lsr r24             mov r24,r1
    clr __zero_reg__    clr __zero_reg__    ldi r25,lo8(37)     clr __zero_reg__
    lsr r24             sub r24,r25         mul r24,r25         lsr r24
    ret                 lsr r24             mov r24,r1          ret
                        add r24,r25         clr __zero_reg__
                        lsr r24             ret                                              
                        lsr r24                                         
                        ret